diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22ae067c7..1c77c5b80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,9 +6,6 @@ on: - master - develop pull_request: - branches: - - master - - develop env: IS_ORIGINAL_REPO: ${{ github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') }} @@ -143,6 +140,57 @@ jobs: # macOS Qt6 Universal build macos-qt6-universal: name: macOS (Qt6 Universal) + runs-on: macos-26 + needs: [initialization, code-format-validation] + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '18' + + - name: Install dependencies + run: | + pip3 install --break-system-packages aqtinstall + python3 -m aqt install-qt mac desktop 6.12.0 -m qt5compat qtmultimedia qtimageformats qtshadertools qtspeech + echo "${{ github.workspace }}/6.12.0/macos/bin" >> $GITHUB_PATH + npm install -g appdmg + + - name: Import Code Signing Certificate + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + uses: apple-actions/import-codesign-certs@v2 + with: + p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }} + p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + + - name: Build + env: + MACOSX_DEPLOYMENT_TARGET: "14.4" + run: | + VERSION="$(tr -d '\r\n' < VERSION)" + SKIP_CODESIGN="${{ env.IS_FORK }}" + SKIP_CODESIGN=$(echo "$SKIP_CODESIGN" | tr '[:upper:]' '[:lower:]') + ./compileOSX.sh $VERSION ${{ needs.initialization.outputs.build_number }} $SKIP_CODESIGN Qt6 universal + + - name: Run tests + run: ctest --test-dir build --output-on-failure + + - name: Notarize + if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') + run: | + xcrun notarytool submit *.dmg --apple-id "${{ secrets.MACOS_APPLE_ID }}" --team-id "${{ secrets.MACOS_TEAM_ID }}" --password "${{ secrets.MACOS_APP_PASSWORD }}" --wait + xcrun stapler staple *.dmg + + - name: Upload DMG + uses: actions/upload-artifact@v6 + with: + name: macos-qt6-universal-${{ needs.initialization.outputs.build_number }}-dmg + path: "*.dmg" + + # macOS Qt6 Universal legacy build (macOS 12+, older Qt) + macos-qt6-universal-legacy: + name: macOS (Qt6 Universal Legacy) runs-on: macos-15 needs: [initialization, code-format-validation] steps: @@ -170,6 +218,7 @@ jobs: - name: Build env: MACOSX_DEPLOYMENT_TARGET: "12" + DMG_NAME_SUFFIX: "-legacy" run: | VERSION="$(tr -d '\r\n' < VERSION)" SKIP_CODESIGN="${{ env.IS_FORK }}" @@ -188,7 +237,7 @@ jobs: - name: Upload DMG uses: actions/upload-artifact@v6 with: - name: macos-qt6-universal-${{ needs.initialization.outputs.build_number }}-dmg + name: macos-qt6-universal-legacy-${{ needs.initialization.outputs.build_number }}-dmg path: "*.dmg" # Windows x64 Qt6 build @@ -555,6 +604,7 @@ jobs: - linux-qt6 - linux-qt6-7zip - macos-qt6-universal + - macos-qt6-universal-legacy - windows-x64-qt6 - windows-arm64-qt6 - docker-amd64 @@ -623,6 +673,7 @@ jobs: - linux-qt6 - linux-qt6-7zip - macos-qt6-universal + - macos-qt6-universal-legacy - windows-x64-qt6 - windows-arm64-qt6 - docker-amd64 diff --git a/CHANGELOG.md b/CHANGELOG.md index dab520da9..7f199ea77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,61 @@ Version counting is based on semantic versioning (Major.Feature.Patch) +## 10.5.0 + +### YACReader + +* Add Bicubic as a scaling method in Settings, offering sharper images than Bilinear and faster scaling than Lanczos. +* Fix opening comics at page -1 after removing them from the "Reading" list. +* Improve toolbar on macOS. + +### YACReaderLibrary + +* Reading lists and labels keep comics whose files are removed from the library. Their covers show in gray, with their metadata and last known file name. You can still edit their metadata, get data from Comic Vine, set a custom cover, reorder them, and remove them from the list. When the same file comes back, at any path, the comic is available again. +* Reading lists and labels keep a comic when its file is replaced, for example by a tool that writes ComicInfo.xml into the archive. +* Import reading lists in CBL XML or JSON format. Preview and adjust matches to comics in your library, and keep missing books in the reading order so you can find them later. +* Match missing books to library comics individually or across an entire reading list. +* Export reading lists as CBL XML, JSON, or a web page. +* Create smart lists that show the current results of a saved search. +* Organize reading lists and smart lists in folders. +* Track missing issues in regular folders, using Comic Vine or entering the issue numbers yourself. Edit their metadata and covers, and automatically match newly added files to the missing issues. +* Include the same comic more than once in a reading list, with each appearance keeping its place in the reading order. +* Sort search results by series, volume, and issue number. +* The metadata editor saves only the fields that you change, also when you edit several comics. Comic Vine changes only the fields that it returns. +* Report failed Comic Vine lookups and incomplete results. +* Fix removing comics belonging to child reading lists from a parent list view. +* Fix newly added comics sometimes appearing in the middle of a reading list instead of at the end. +* Fix library lock handling and add a way to clear these locks from the GUI and webui. +* Improve library opening speed over network shares. +* Unify the title and tool bars for a more integrated look (Windows/macOS). + +### Server + +* Add server API v3 so compatible mobile clients can browse missing comics, reading lists, and smart lists, including their folders, with content ordered as on the desktop. Existing clients remain supported. +* Fix a security issue that could let cover requests access image files outside the library's covers folder. +* Fix memory leak updating libraries. +* Improve covers API performance. + +### WebUI + +* Browse reading lists, smart lists, their folders, Favorites, tags, and Recent from the library menu. +* View missing comics in reading lists and folders, including their available metadata and covers. +* Choose how many days count as recent in this browser's settings. +* Improve the home page and add a connect device dialog. iOS and Android clients can use now the QR scaner with the webui. +* Add mouse wheel page navigation to the reader. +* Add PageUp, PageDown, Home and End keys to the reader. +* The back button now returns to where you were, keeping the scroll position. +* Save the reading progress when the tab goes to the background. +* Show better error messages. +* Add a favicon. +* Open read comics on the first page. +* Go to the next or previous comic from the reader. Comics opened from a list follow the order of that list, all others follow their folder. + +### All GUI apps + +* Fix the apps sometimes getting stuck in fullscreen mode after a restart. YACReaderLibrary no longer starts in fullscreen mode, and YACReader still does when you close it in fullscreen mode, but you can always leave it. + + ## 10.4.0 ### YACReader diff --git a/CMakeLists.txt b/CMakeLists.txt index d7a1ffc72..702388e8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # Compiler options (MSVC flags) include(cmake/CompilerOptions.cmake) +include(cmake/SevenZipRuntime.cmake) # --- Build options --- @@ -127,6 +128,7 @@ if(BUILD_SERVER_STANDALONE) ) else() find_package(Qt6 6.9 REQUIRED COMPONENTS + Concurrent Core Core5Compat Gui @@ -169,6 +171,7 @@ if(NOT BUILD_SERVER_STANDALONE) endif() add_subdirectory(YACReaderLibrary/server) +add_subdirectory(YACReaderLibrary/cbl) if(NOT BUILD_SERVER_STANDALONE) add_subdirectory(YACReaderLibrary/comic_vine) diff --git a/VERSION b/VERSION index 7400abcdf..fcc198cb8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.4.0 \ No newline at end of file +10.5.0 \ No newline at end of file diff --git a/YACReader/CMakeLists.txt b/YACReader/CMakeLists.txt index 42f512e78..e87209b97 100644 --- a/YACReader/CMakeLists.txt +++ b/YACReader/CMakeLists.txt @@ -229,18 +229,22 @@ qt_add_resources(YACReader "yacreader_common_theme_images" ) # Translations +set(yacreader_translation_source_targets + YACReader + comic_backend + common_gui + custom_widgets_reader + shortcuts_reader +) qt_add_translations(YACReader - SOURCE_TARGETS - YACReader - comic_backend - common_gui - custom_widgets_reader - shortcuts_reader + SOURCE_TARGETS ${yacreader_translation_source_targets} + LUPDATE_OPTIONS -no-obsolete TS_FILES yacreader_es.ts yacreader_fr.ts yacreader_ru.ts yacreader_pt.ts + yacreader_pt_PT.ts yacreader_nl.ts yacreader_tr.ts yacreader_de.ts @@ -249,9 +253,14 @@ qt_add_translations(YACReader yacreader_zh_HK.ts yacreader_it.ts yacreader_ko.ts - yacreader_source.ts yacreader_en.ts ) +qt_add_lupdate( + SOURCE_TARGETS ${yacreader_translation_source_targets} + OPTIONS -no-obsolete + TS_FILES yacreader_source.ts + LUPDATE_TARGET YACReader_source_lupdate +) target_link_libraries(YACReader PRIVATE Qt6::Core @@ -277,7 +286,7 @@ target_link_libraries(YACReader PRIVATE if(WIN32) target_sources(YACReader PRIVATE icon.rc) if(MSVC) - target_link_options(YACReader PRIVATE "/MANIFESTINPUT:${PROJECT_SOURCE_DIR}/cmake/windows/yacreader.manifest") + target_sources(YACReader PRIVATE ${PROJECT_SOURCE_DIR}/cmake/windows/yacreader.manifest) endif() target_link_libraries(YACReader PRIVATE oleaut32 ole32 shell32 user32) endif() diff --git a/YACReader/bookmarks_dialog.cpp b/YACReader/bookmarks_dialog.cpp index 6348d7016..95f9153c1 100644 --- a/YACReader/bookmarks_dialog.cpp +++ b/YACReader/bookmarks_dialog.cpp @@ -24,7 +24,7 @@ BookmarksDialog::BookmarksDialog(QWidget *parent) // bookmarks auto bookmarksL = new QGridLayout(); - pages.push_back(new QLabel(tr("Lastest Page"))); + pages.push_back(new QLabel(tr("Latest Page"))); for (int i = 0; i < 3; i++) pages.push_back(new QLabel("-")); diff --git a/YACReader/goto_dialog.cpp b/YACReader/goto_dialog.cpp index d0c5b12ed..dc5383c6d 100644 --- a/YACReader/goto_dialog.cpp +++ b/YACReader/goto_dialog.cpp @@ -13,7 +13,7 @@ GoToDialog::GoToDialog(QWidget *parent) void GoToDialog::setupUI() { - textLabel = new QLabel(tr("Page : ")); + textLabel = new QLabel(tr("Page: ")); pageNumber = new QLineEdit; v = new QIntValidator(this); v->setBottom(1); @@ -37,7 +37,7 @@ void GoToDialog::setupUI() bottomLayout->addWidget(cancel); auto mainLayout = new QVBoxLayout; - mainLayout->addWidget(numPagesLabel = new QLabel(tr("Total pages : "))); + mainLayout->addWidget(numPagesLabel = new QLabel(tr("Total pages: "))); mainLayout->addLayout(topLayout); mainLayout->addStretch(); mainLayout->addLayout(bottomLayout); @@ -67,7 +67,7 @@ void GoToDialog::goTo() void GoToDialog::setNumPages(unsigned int numPages) { - numPagesLabel->setText(tr("Total pages : ") + QString::number(numPages)); + numPagesLabel->setText(tr("Total pages: ") + QString::number(numPages)); v->setTop(numPages); } diff --git a/YACReader/goto_flow_toolbar.cpp b/YACReader/goto_flow_toolbar.cpp index 5905d76b8..668cc3b95 100644 --- a/YACReader/goto_flow_toolbar.cpp +++ b/YACReader/goto_flow_toolbar.cpp @@ -29,7 +29,7 @@ GoToFlowToolBar::GoToFlowToolBar(QWidget *parent) connect(slider, &QSlider::valueChanged, this, &GoToFlowToolBar::setCenter); connect(slider, &QSlider::valueChanged, this, &GoToFlowToolBar::setPage); - pageHint = new QLabel("" + tr("Page : ") + "", this); + pageHint = new QLabel("" + tr("Page: ") + "", this); v = new QIntValidator(); v->setBottom(1); edit = new QLineEdit(); diff --git a/YACReader/main.cpp b/YACReader/main.cpp index 10c39f04b..384778f45 100644 --- a/YACReader/main.cpp +++ b/YACReader/main.cpp @@ -137,6 +137,8 @@ int main(int argc, char *argv[]) QCommandLineOption comicId("comicId", "", "comicId"); QCommandLineOption libraryId("libraryId", "", "libraryId"); QCommandLineOption readingListId("readingListId", "", "readingListId"); + QCommandLineOption readingListEntryId("readingListEntryId", "", "readingListEntryId"); + readingListEntryId.setFlags(QCommandLineOption::HiddenFromHelp); // hide comicId and libraryId from help comicId.setFlags(QCommandLineOption::HiddenFromHelp); libraryId.setFlags(QCommandLineOption::HiddenFromHelp); @@ -146,6 +148,7 @@ int main(int argc, char *argv[]) parser.addOption(comicId); parser.addOption(libraryId); parser.addOption(readingListId); + parser.addOption(readingListEntryId); parser.process(app); QString destLog = YACReader::getSettingsPath() + "/yacreader.log"; @@ -186,7 +189,7 @@ int main(int argc, char *argv[]) OpenComicSource source; if (parser.isSet(readingListId)) { - source = OpenComicSource { OpenComicSource::ReadingList, parser.value(readingListId).toULongLong() }; + source = OpenComicSource { OpenComicSource::ReadingList, parser.value(readingListId).toULongLong(), parser.value(readingListEntryId).toULongLong() }; } else { source = OpenComicSource { OpenComicSource::Folder, 33 }; // Folder is not needed to get the comic information, the comid id points to a unique comic } diff --git a/YACReader/main_window_viewer.cpp b/YACReader/main_window_viewer.cpp index 7970cece2..80080a525 100644 --- a/YACReader/main_window_viewer.cpp +++ b/YACReader/main_window_viewer.cpp @@ -180,7 +180,7 @@ void MainWindowViewer::syncModeActions(bool manga, bool continuousScroll) } } - doubleMangaPageSwitch(); + refreshPrevNextActions(); } MainWindowViewer::~MainWindowViewer() @@ -267,6 +267,10 @@ void MainWindowViewer::setupUI() move(QPoint((widthDesktopResolution - width) / 2, ((heightDesktopResolution - height) - 40) / 2)); resize(QSize(width, height)); } else { + // Only the FULLSCREEN setting starts the viewer in full screen (see toFullScreen() below); + // a full screen state in the saved geometry would leave fullscreen out of sync. + if (isFullScreen()) + setWindowState(windowState() & ~Qt::WindowFullScreen); // Guard against the window landing off-screen when a monitor is unplugged // between sessions. Qt 6 tries to remap the geometry to the primary screen // when the saved screen is gone, but the result can still be off-screen. @@ -516,10 +520,10 @@ void MainWindowViewer::createActions() doubleMangaPageAction->setData(DOUBLE_MANGA_PAGE_ACTION_Y); doubleMangaPageAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(DOUBLE_MANGA_PAGE_ACTION_Y)); connect(doubleMangaPageAction, &QAction::triggered, viewer, &Viewer::doubleMangaPageSwitch); - connect(doubleMangaPageAction, &QAction::triggered, this, &MainWindowViewer::doubleMangaPageSwitch); + connect(doubleMangaPageAction, &QAction::triggered, this, &MainWindowViewer::refreshPrevNextActions); goToPageAction = new QAction(tr("Go To"), this); - goToPageAction->setToolTip(tr("Go to page ...")); + goToPageAction->setToolTip(tr("Go to page...")); goToPageAction->setData(GO_TO_PAGE_ACTION_Y); goToPageAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(GO_TO_PAGE_ACTION_Y)); connect(goToPageAction, &QAction::triggered, viewer, &Viewer::showGoToDialog); @@ -918,6 +922,8 @@ void MainWindowViewer::openComicFromRecentAction(QAction *action) openFolderFromPath(action->data().toString()); } } else { + // these neighbours belong to the comic shown before, so the reset must not use them + previousComicPath = nextComicPath = ""; viewer->resetContent(); viewer->showMessageErrorOpening(); } @@ -959,17 +965,13 @@ void MainWindowViewer::open(QString path, ComicDB &comic, QList &siblin QFileInfo fi(path); currentComicPath = fi.absoluteFilePath(); - if (!comic.info.title.isNull() && !comic.info.title.toString().isEmpty()) - setWindowTitle("YACReader - " + comic.info.title.toString()); - else - setWindowTitle("YACReader - " + fi.fileName()); + setWindowTitle("YACReader - " + titleOrFileName(comic.info, fi.fileName())); applyLibraryReaderMode(comic.info.type.value()); viewer->open(path, comic); enableActions(); - int index = siblings.indexOf(comic); - updatePrevNextActions(index > 0, index + 1 < siblings.count()); + updatePrevNextActions(currentSiblingIndex > 0, currentSiblingIndex >= 0 && currentSiblingIndex + 1 < siblings.count()); optionsDialog->setFilters(currentComicDB.info.brightness, currentComicDB.info.contrast, currentComicDB.info.gamma); } @@ -983,16 +985,16 @@ void MainWindowViewer::open(QString path, qint64 comicId, qint64 libraryId, YACR enableActions(); - currentComicDB.id = comicId; + currentComicDB.file.id = ComicId(comicId); YACReaderLocalClient client; int tries = 1; bool success = false; - while (!(success = client.requestComicInfo(libraryId, currentComicDB, siblingComics, source)) && tries != 0) + while (!(success = client.requestComicInfo(libraryId, currentComicDB, siblingComics, currentSiblingIndex, source)) && tries != 0) tries--; if (success) { isClient = true; - open(path + currentComicDB.path, currentComicDB, siblingComics); + open(path + currentComicDB.file.path, currentComicDB, siblingComics); } else { isClient = false; QMessageBox::information(this, "Connection Error", "Unable to connect to YACReaderLibrary"); @@ -1251,6 +1253,7 @@ void MainWindowViewer::toFullScreen() hideToolBars(); viewer->hide(); viewer->fullscreen = true; // TODO, change by the right use of windowState(); + geometryBeforeFullScreen = saveGeometry(); setWindowState(Qt::WindowFullScreen); viewer->show(); if (viewer->magnifyingGlassIsVisible()) @@ -1340,8 +1343,7 @@ void MainWindowViewer::checkNewVersion() void MainWindowViewer::processReset() { disableActions(); - if (!isClient || siblingComics.size() <= 1) - disablePreviousNextComicActions(); + refreshPrevNextActions(); } void MainWindowViewer::setUpShortcutsManagement() @@ -1350,8 +1352,8 @@ void MainWindowViewer::setUpShortcutsManagement() QMap> iconMapping; iconMapping[tr("Comics")] = [](const Theme &t) { return t.shortcutsIcons.comicsIcon; }; iconMapping[tr("General")] = [](const Theme &t) { return t.shortcutsIcons.generalIcon; }; - iconMapping[tr("Magnifiying glass")] = [](const Theme &t) { return t.shortcutsIcons.magnifyingGlassIcon; }; - iconMapping[tr("Page adjustement")] = [](const Theme &t) { return t.shortcutsIcons.pageIcon; }; + iconMapping[tr("Magnifying glass")] = [](const Theme &t) { return t.shortcutsIcons.magnifyingGlassIcon; }; + iconMapping[tr("Page adjustment")] = [](const Theme &t) { return t.shortcutsIcons.pageIcon; }; iconMapping[tr("Reading")] = [](const Theme &t) { return t.shortcutsIcons.readingIcon; }; editShortcutsDialog->setGroupIconMapping(iconMapping); @@ -1416,7 +1418,7 @@ void MainWindowViewer::setUpShortcutsManagement() zoomInMglassAction, zoomOutMglassAction, resetMglassAction }; - editShortcutsDialog->addActionsGroup(tr("Magnifiying glass"), theme.shortcutsIcons.magnifyingGlassIcon, + editShortcutsDialog->addActionsGroup(tr("Magnifying glass"), theme.shortcutsIcons.magnifyingGlassIcon, tmpList = QList() << showMagnifyingGlassAction << mglassActions); @@ -1427,7 +1429,7 @@ void MainWindowViewer::setUpShortcutsManagement() CHANGE_FIT_ACTION_Y); connect(toggleFitToScreenAction, &QAction::triggered, this, &MainWindowViewer::toggleWidthHeight); - editShortcutsDialog->addActionsGroup(tr("Page adjustement"), theme.shortcutsIcons.pageIcon, + editShortcutsDialog->addActionsGroup(tr("Page adjustment"), theme.shortcutsIcons.pageIcon, tmpList = QList() << adjustHeightAction << adjustWidthAction @@ -1519,11 +1521,10 @@ void MainWindowViewer::setUpShortcutsManagement() ShortcutsManager::getShortcutsManager().registerActions(allActions); } -void MainWindowViewer::doubleMangaPageSwitch() +void MainWindowViewer::refreshPrevNextActions() { if (isClient) { - int index = siblingComics.indexOf(currentComicDB); - updatePrevNextActions(index > 0, index + 1 < siblingComics.size()); + updatePrevNextActions(currentSiblingIndex > 0, currentSiblingIndex >= 0 && currentSiblingIndex + 1 < siblingComics.size()); } else { updatePrevNextActions(!previousComicPath.isEmpty(), !nextComicPath.isEmpty()); } @@ -1584,10 +1585,9 @@ void MainWindowViewer::closeEvent(QCloseEvent *event) viewer->save(); Configuration &conf = Configuration::getConfiguration(); - // saveGeometry() encodes the current screen, the maximized/fullscreen state, and - // the normal (pre-maximize) geometry in one blob, so restoreGeometry() puts the - // window back on the right monitor in the right state regardless of how it was closed. - conf.setGeometry(saveGeometry()); + // In full screen, save the geometry from before it: it keeps the maximized state, which + // showFullScreen() clears. Full screen at start comes from the FULLSCREEN setting. + conf.setGeometry(fullscreen ? geometryBeforeFullScreen : saveGeometry()); event->accept(); } @@ -1597,13 +1597,10 @@ void MainWindowViewer::openPreviousComic() if (!siblingComics.isEmpty() && isClient) { sendComic(); - int currentIndex = siblingComics.indexOf(currentComicDB); - if (currentIndex == -1) - return; - if (currentIndex - 1 >= 0 && currentIndex - 1 < siblingComics.count()) { - siblingComics[currentIndex] = currentComicDB; // updated - currentComicDB = siblingComics.at(currentIndex - 1); - open(currentDirectory + currentComicDB.path, currentComicDB, siblingComics); + if (currentSiblingIndex - 1 >= 0 && currentSiblingIndex - 1 < siblingComics.count()) { + storeCurrentComicInSiblings(); + currentComicDB = siblingComics.at(--currentSiblingIndex); + open(currentDirectory + currentComicDB.file.path, currentComicDB, siblingComics); } return; } @@ -1617,13 +1614,10 @@ void MainWindowViewer::openNextComic() if (!siblingComics.isEmpty() && isClient) { sendComic(); - int currentIndex = siblingComics.indexOf(currentComicDB); - if (currentIndex == -1) - return; - if (currentIndex + 1 > 0 && currentIndex + 1 < siblingComics.count()) { - siblingComics[currentIndex] = currentComicDB; // updated - currentComicDB = siblingComics.at(currentIndex + 1); - open(currentDirectory + currentComicDB.path, currentComicDB, siblingComics); + if (currentSiblingIndex + 1 > 0 && currentSiblingIndex + 1 < siblingComics.count()) { + storeCurrentComicInSiblings(); + currentComicDB = siblingComics.at(++currentSiblingIndex); + open(currentDirectory + currentComicDB.file.path, currentComicDB, siblingComics); } return; @@ -1821,7 +1815,6 @@ void MainWindowViewer::dragEnterEvent(QDragEnterEvent *event) // accept just text/uri-list mime format if (event->mimeData()->hasFormat("text/uri-list")) { event->acceptProposedAction(); - isClient = false; } } @@ -1864,14 +1857,13 @@ void MainWindowViewer::sendComic() viewer->updateComic(currentComicDB); - int currentIndex = siblingComics.indexOf(currentComicDB); - bool sendNextComicInfo = (currentComicDB.info.currentPage == currentComicDB.info.numPages) && (currentIndex + 1 > 0 && currentIndex + 1 < siblingComics.count()); + bool sendNextComicInfo = (currentComicDB.info.currentPage == currentComicDB.info.numPages) && (currentSiblingIndex + 1 > 0 && currentSiblingIndex + 1 < siblingComics.count()); if (sendNextComicInfo) { - ComicDB &nextComic = siblingComics[currentIndex + 1]; + ComicDB &nextComic = siblingComics[currentSiblingIndex + 1]; nextComic.info.hasBeenOpened = true; int retries = 1; - while (!client->sendComicInfo(libraryId, currentComicDB, nextComic.id) && retries != 0) + while (!client->sendComicInfo(libraryId, currentComicDB, nextComic.file.id.value()) && retries != 0) retries--; } else { int retries = 1; @@ -1880,6 +1872,15 @@ void MainWindowViewer::sendComic() } } +// the reading state belongs to the comic, so every occurrence gets it +void MainWindowViewer::storeCurrentComicInSiblings() +{ + for (auto &sibling : siblingComics) { + if (sibling == currentComicDB) + sibling = currentComicDB; + } +} + void MainWindowViewer::updatePrevNextActions(bool thereIsPrevious, bool thereIsNext) { if (thereIsPrevious) { diff --git a/YACReader/main_window_viewer.h b/YACReader/main_window_viewer.h index d960422d6..15a31fd43 100644 --- a/YACReader/main_window_viewer.h +++ b/YACReader/main_window_viewer.h @@ -77,7 +77,7 @@ public slots: void checkNewVersion(); void processReset(); void setUpShortcutsManagement(); - void doubleMangaPageSwitch(); + void refreshPrevNextActions(); void toggleFitToWidthSlider(); @@ -95,6 +95,7 @@ public slots: bool fullscreen; bool toolbars; bool fromMaximized; + QByteArray geometryBeforeFullScreen; QString currentDirectory; QString currentComicPath; @@ -195,6 +196,9 @@ public slots: ComicDB currentComicDB; QList siblingComics; + // a reading list can have a comic more than once, so the current comic is tracked by position + qint32 currentSiblingIndex = -1; + void storeCurrentComicInSiblings(); bool isClient; QString startComicPath; quint64 libraryId; diff --git a/YACReader/options_dialog.cpp b/YACReader/options_dialog.cpp index 3a59a6a27..1b5bfc995 100644 --- a/YACReader/options_dialog.cpp +++ b/YACReader/options_dialog.cpp @@ -105,7 +105,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) connect(clearBackgroundColorButton, &QAbstractButton::clicked, this, &OptionsDialog::clearBackgroundColor); colorBox->setLayout(colorSelection); - auto scrollBox = new QGroupBox(tr("Scroll behaviour")); + auto scrollBox = new QGroupBox(tr("Scroll behavior")); auto scrollLayout = new QVBoxLayout; disableScrollAnimations = new QCheckBox(tr("Disable scroll animations and smooth scrolling")); @@ -123,7 +123,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) normalMouseModeRadioButton = new QRadioButton(tr("Only Back/Forward buttons can turn pages")); leftRightNavigationMouseModeRadioButton = new QRadioButton(tr("Use the Left/Right buttons to turn pages.")); - hotAreasMouseModeRadioButton = new QRadioButton(tr("Click left or right half of the screen to turn pages.")); + hotAreasMouseModeRadioButton = new QRadioButton(tr("Click the left or right half of the screen to turn pages.")); mouseModeLayout->addWidget(normalMouseModeRadioButton); mouseModeLayout->addWidget(leftRightNavigationMouseModeRadioButton); @@ -275,6 +275,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) scalingMethodCombo->addItem(tr("Nearest (fast, low quality)")); scalingMethodCombo->addItem(tr("Bilinear")); scalingMethodCombo->addItem(tr("Lanczos (better quality)")); + scalingMethodCombo->addItem(tr("Bicubic")); connect(scalingMethodCombo, &QComboBox::currentIndexChanged, this, [this](int index) { Configuration::getConfiguration().setScalingMethod(static_cast(index)); emit changedImageOptions(); diff --git a/YACReader/themes/builtin_classic.json b/YACReader/themes/builtin_classic.json index ac1bda11e..4f1eabc0e 100644 --- a/YACReader/themes/builtin_classic.json +++ b/YACReader/themes/builtin_classic.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#55000000", "editBorderColor": "#77000000", "editTextColor": "#FFFFFF", - "flowBackgroundColor": "#282828", + "flowBackgroundColor": "#191919", "flowTextColor": "#FFFFFF", "iconColor": "#FFFFFF", "labelTextColor": "#FFFFFF", diff --git a/YACReader/themes/builtin_dark.json b/YACReader/themes/builtin_dark.json index 40dc2dd2a..2753d3c35 100644 --- a/YACReader/themes/builtin_dark.json +++ b/YACReader/themes/builtin_dark.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#55000000", "editBorderColor": "#77000000", "editTextColor": "#FFFFFF", - "flowBackgroundColor": "#282828", + "flowBackgroundColor": "#191919", "flowTextColor": "#FFFFFF", "iconColor": "#CCCCCC", "labelTextColor": "#FFFFFF", diff --git a/YACReader/themes/builtin_dark1.json b/YACReader/themes/builtin_dark1.json index 23e5d7130..74f8a253c 100644 --- a/YACReader/themes/builtin_dark1.json +++ b/YACReader/themes/builtin_dark1.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#AA403E41", "editBorderColor": "#885B595C", "editTextColor": "#C1C0C0", - "flowBackgroundColor": "#221F22", + "flowBackgroundColor": "#161416", "flowTextColor": "#939293", "iconColor": "#FCFCFA", "labelTextColor": "#FCFCFA", diff --git a/YACReader/themes/builtin_dark2.json b/YACReader/themes/builtin_dark2.json index b3e3a6ace..643eedd96 100644 --- a/YACReader/themes/builtin_dark2.json +++ b/YACReader/themes/builtin_dark2.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#AA424450", "editBorderColor": "#88424450", "editTextColor": "#EDEDF0", - "flowBackgroundColor": "#282A36", + "flowBackgroundColor": "#1A1B23", "flowTextColor": "#6C789C", "iconColor": "#F8F8F2", "labelTextColor": "#F8F8F2", diff --git a/YACReader/themes/builtin_dark3.json b/YACReader/themes/builtin_dark3.json index 1d43cf455..345f1d027 100644 --- a/YACReader/themes/builtin_dark3.json +++ b/YACReader/themes/builtin_dark3.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#AA3C3836", "editBorderColor": "#88504945", "editTextColor": "#D5C4A1", - "flowBackgroundColor": "#282828", + "flowBackgroundColor": "#191919", "flowTextColor": "#928374", "iconColor": "#EBDBB2", "labelTextColor": "#EBDBB2", diff --git a/YACReader/themes/builtin_dark4.json b/YACReader/themes/builtin_dark4.json index 5d98ecc23..e1b6f14d4 100644 --- a/YACReader/themes/builtin_dark4.json +++ b/YACReader/themes/builtin_dark4.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#AA434C5E", "editBorderColor": "#884C566A", "editTextColor": "#E5E9F0", - "flowBackgroundColor": "#2E3440", + "flowBackgroundColor": "#20242D", "flowTextColor": "#787D88", "iconColor": "#ECEFF4", "labelTextColor": "#ECEFF4", diff --git a/YACReader/themes/builtin_light.json b/YACReader/themes/builtin_light.json index 4027bc539..2dc852e9c 100644 --- a/YACReader/themes/builtin_light.json +++ b/YACReader/themes/builtin_light.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#22000000", "editBorderColor": "#33000000", "editTextColor": "#202020", - "flowBackgroundColor": "#F6F6F6", + "flowBackgroundColor": "#DEDEDE", "flowTextColor": "#202020", "iconColor": "#404040", "labelTextColor": "#202020", diff --git a/YACReader/themes/builtin_light1.json b/YACReader/themes/builtin_light1.json index f19822fe3..c239247c7 100644 --- a/YACReader/themes/builtin_light1.json +++ b/YACReader/themes/builtin_light1.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#CCF2F4F8", "editBorderColor": "#66CCD0DA", "editTextColor": "#4C4F69", - "flowBackgroundColor": "#E6E9EF", + "flowBackgroundColor": "#D0D3D9", "flowTextColor": "#5C5F77", "iconColor": "#4C4F69", "labelTextColor": "#4C4F69", diff --git a/YACReader/themes/builtin_light2.json b/YACReader/themes/builtin_light2.json index bac201e04..44a32e06a 100644 --- a/YACReader/themes/builtin_light2.json +++ b/YACReader/themes/builtin_light2.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#CCEEF1F8", "editBorderColor": "#66C4C8DA", "editTextColor": "#2F334D", - "flowBackgroundColor": "#D5D6DB", + "flowBackgroundColor": "#BFC0C5", "flowTextColor": "#4A5685", "iconColor": "#2F334D", "labelTextColor": "#2F334D", diff --git a/YACReader/themes/builtin_light3.json b/YACReader/themes/builtin_light3.json index 37c822007..81e403892 100644 --- a/YACReader/themes/builtin_light3.json +++ b/YACReader/themes/builtin_light3.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#CCFBF7EA", "editBorderColor": "#66E6E2CC", "editTextColor": "#4F5B58", - "flowBackgroundColor": "#EFEBD4", + "flowBackgroundColor": "#D9D5BE", "flowTextColor": "#5C6A72", "iconColor": "#4F5B58", "labelTextColor": "#4F5B58", diff --git a/YACReader/themes/builtin_light4.json b/YACReader/themes/builtin_light4.json index d7f33aa62..577e38227 100644 --- a/YACReader/themes/builtin_light4.json +++ b/YACReader/themes/builtin_light4.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#CCFFF8F1", "editBorderColor": "#66D9CEC7", "editTextColor": "#575279", - "flowBackgroundColor": "#E8DDD3", + "flowBackgroundColor": "#D2C7BD", "flowTextColor": "#6E6A86", "iconColor": "#575279", "labelTextColor": "#575279", diff --git a/YACReader/themes/builtin_light5.json b/YACReader/themes/builtin_light5.json index 3cb78d05f..88051e322 100644 --- a/YACReader/themes/builtin_light5.json +++ b/YACReader/themes/builtin_light5.json @@ -6,7 +6,7 @@ "editBackgroundColor": "#CCFCF7ED", "editBorderColor": "#66D8CDBA", "editTextColor": "#545464", - "flowBackgroundColor": "#E7DCCB", + "flowBackgroundColor": "#D1C6B5", "flowTextColor": "#716E61", "iconColor": "#545464", "labelTextColor": "#545464", diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp index 4925b9c40..100d40ccd 100644 --- a/YACReader/viewer.cpp +++ b/YACReader/viewer.cpp @@ -118,11 +118,11 @@ Viewer::Viewer(QWidget *parent) content->setAlignment(Qt::AlignTop | Qt::AlignHCenter); content->setMouseTracking(true); - // dedicated widget for status messages ("Press 'O' to open comic.", "Loading...", etc.) + // dedicated widget for status messages ("Press 'O' to open a comic.", "Loading...", etc.) messageLabel = new QLabel(this); messageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); messageLabel->setAlignment(Qt::AlignTop | Qt::AlignHCenter); - messageLabel->setText(tr("Press 'O' to open comic.")); + messageLabel->setText(tr("Press 'O' to open a comic.")); messageLabel->setFont(QFont("courier new", 12)); messageLabel->setMouseTracking(true); @@ -1812,7 +1812,7 @@ void Viewer::doubleMangaPageSwitch() void Viewer::resetContent() { - configureContent(tr("Press 'O' to open comic.")); + configureContent(tr("Press 'O' to open a comic.")); goToFlow->reset(); continuousViewModel->reset(); continuousPageProvider->reset(); @@ -1829,7 +1829,7 @@ void Viewer::setLoadingMessage() } emit pageAvailable(false); if (!continuousScroll) { - configureContent(tr("Loading...please wait!")); + configureContent(tr("Loading... please wait!")); } } diff --git a/YACReader/yacreader_de.ts b/YACReader/yacreader_de.ts index c89cef1ab..180a06945 100644 --- a/YACReader/yacreader_de.ts +++ b/YACReader/yacreader_de.ts @@ -19,12 +19,12 @@ System - Systemumgebung + System Light - Licht + Hell @@ -34,7 +34,7 @@ Custom - Brauch + Benutzerdefiniert @@ -44,12 +44,12 @@ Remove this user-imported theme - Entfernen Sie dieses vom Benutzer importierte Design + Dieses vom Benutzer importierte Design entfernen Light: - Licht: + Hell: @@ -64,37 +64,37 @@ Import theme... - Theme importieren... + Design importieren... Theme - Thema + Design Theme editor - Theme-Editor + Design-Editor Open Theme Editor... - Theme-Editor öffnen... + Design-Editor öffnen... Theme editor error - Fehler im Theme-Editor + Fehler im Design-Editor The current theme JSON could not be loaded. - Der aktuelle Theme-JSON konnte nicht geladen werden. + Das JSON des aktuellen Designs konnte nicht geladen werden. Import theme - Thema importieren + Design importieren @@ -105,7 +105,7 @@ Could not import theme from: %1 - Theme konnte nicht importiert werden von: + Das Design konnte nicht aus folgender Datei importiert werden: %1 @@ -114,7 +114,7 @@ %1 %2 - Theme konnte nicht importiert werden von: + Das Design konnte nicht aus folgender Datei importiert werden: %1 %2 @@ -122,7 +122,7 @@ Import failed - Der Import ist fehlgeschlagen + Import fehlgeschlagen @@ -130,7 +130,7 @@ Close - Schliessen + Schließen @@ -141,12 +141,12 @@ Click on any image to go to the bookmark - Klicke auf ein beliebiges Bild, um zum Lesezeichen zu gehen + Klicken Sie auf ein beliebiges Bild, um zum Lesezeichen zu gehen - Lastest Page - Neueste Seite + Latest Page + Letzte Seite @@ -154,7 +154,7 @@ Loading page %1 - Seite wird geladen %1 + Seite %1 wird geladen @@ -166,8 +166,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - Um ein Kürzel zu ändern, klicke doppelt auf die Tastenkombination und füge die neuen Tasten ein. + To change a shortcut, double-click the key combination and type the new keys. + Um ein Kürzel zu ändern, doppelklicken Sie auf die Tastenkombination und geben Sie die neuen Tasten ein. @@ -177,36 +177,36 @@ Shortcut in use - Genutzte Kürzel + Kürzel wird verwendet - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function Das Kürzel "%1" ist bereits für eine andere Funktion in Verwendung FileComic - - + + Format not supported Format wird nicht unterstützt - + 7z not found 7z nicht gefunden - + Unknown error opening the file - Unbekannter Fehler beim Öffnen des Files + Unbekannter Fehler beim Öffnen der Datei - + CRC error on page (%1): some of the pages will not be displayed correctly - CRC Error auf Seite (%1): Einige Seiten werden nicht korrekt dargestellt + CRC-Fehler auf Seite (%1): Einige Seiten werden nicht korrekt dargestellt @@ -224,8 +224,8 @@ - Total pages : - Seiten gesamt : + Total pages: + Seiten gesamt: @@ -234,16 +234,16 @@ - Page : - Seite : + Page: + Seite: GoToFlowToolBar - Page : - Seite : + Page: + Seite: @@ -261,7 +261,7 @@ Changelog - + Änderungsprotokoll @@ -274,7 +274,7 @@ Gamma - Gammawert + Gamma @@ -284,7 +284,7 @@ My comics path - Meine Comics-Pfad + Pfad zu meinen Comics @@ -304,12 +304,12 @@ Nearest (fast, low quality) - Am nächsten (schnell, niedrige Qualität) + Nächster Nachbar (schnell, niedrige Qualität) Bilinear - Bilinear-Filter + Bilinear @@ -317,7 +317,12 @@ Lanczos (bessere Qualität) - + + Bicubic + + + + Image adjustment Bildanpassung @@ -342,12 +347,12 @@ Kontrast - + Appearance Aussehen - + Options Optionen @@ -372,9 +377,9 @@ Löschen - + Comics directory - Comics-Verzeichnis + Comics-Ordner @@ -382,12 +387,12 @@ Hintergrundfarbe - + Page Flow Seitenfluss - + General Allgemein @@ -397,7 +402,7 @@ Helligkeit - + Restart is needed Neustart erforderlich @@ -414,7 +419,7 @@ Show time in current page information label - Zeit im Informationsetikett der aktuellen Seite anzeigen + Uhrzeit in der Informationsanzeige der aktuellen Seite anzeigen @@ -438,7 +443,7 @@ - Scroll behaviour + Scroll behavior Scrollverhalten @@ -449,12 +454,12 @@ Do not turn page using scroll - Blättern Sie nicht mit dem Scrollen um + Nicht mit dem Scrollen umblättern Use single scroll step to turn page - Verwenden Sie einen einzelnen Bildlaufschritt, um die Seite umzublättern + Einzelnen Scrollschritt zum Umblättern verwenden @@ -473,7 +478,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Klicken Sie auf die linke oder rechte Hälfte des Bildschirms, um die Seiten umzublättern. @@ -509,7 +514,7 @@ If none is active, Escape does nothing. Tooltip listing the order in which modes are cancelled. Only the first active mode in the list is cancelled per Escape keypress. Die Escape-Taste beendet den ersten aktiven Modus in dieser Reihenfolge: -1. Lupe +1. Vergrößerungsglas 2. Wörterbuch 3. „Gehe zu Seite“-Leiste 4. Vollbildmodus @@ -529,7 +534,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Disable mouse over activation - Aktivierung durch Maus deaktivieren + Aktivierung beim Überfahren mit der Maus deaktivieren @@ -539,7 +544,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Enlarge images to fit width/height - Bilder vergrößern, um sie Breite/Höhe anzupassen + Bilder vergrößern, um sie an Breite/Höhe anzupassen @@ -557,20 +562,20 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. 7z lib not found - 7z-Verzeichnis nicht gefunden + 7z-Bibliothek nicht gefunden unable to load 7z lib from ./utils - 7z -erzeichnis kann von ./utils nicht geladen werden + 7z-Bibliothek kann nicht aus ./utils geladen werden - + Select custom cover Wählen Sie ein benutzerdefiniertes Cover - + Images (%1) Bilder (%1) @@ -582,7 +587,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. This theme is for %1, not %2. - Dieses Thema ist für %1, nicht für %2. + Dieses Design ist für %1, nicht für %2. @@ -590,7 +595,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Theme Editor - Theme-Editor + Design-Editor @@ -605,7 +610,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. i - ich + i @@ -620,7 +625,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Halten Sie gedrückt, um den ausgewählten Wert in der Benutzeroberfläche zu blinken (Magenta / umgeschaltet / 0↔10). Loslassen stellt das Original wieder her. + Halten Sie gedrückt, um den ausgewählten Wert in der Benutzeroberfläche blinken zu lassen (Magenta / umgeschaltet / 0↔10). Loslassen stellt das Original wieder her. @@ -630,7 +635,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Light - Licht + Hell @@ -640,7 +645,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. ID: - AUSWEIS: + ID: @@ -655,12 +660,12 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Theme info - Themeninfo + Design-Info Parameter - Parameterwert + Parameter @@ -685,7 +690,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Close - Schliessen + Schließen @@ -700,7 +705,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. true - WAHR + true @@ -708,7 +713,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. false - FALSCH + false @@ -730,7 +735,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Save theme - Thema speichern + Design speichern @@ -753,7 +758,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Load theme - Theme laden + Design laden @@ -773,7 +778,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Invalid JSON: %1 - Ungültiger JSON: + Ungültiges JSON: %1 @@ -792,7 +797,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - Press 'O' to open comic. + Press 'O' to open a comic. 'O' drücken, um Comic zu öffnen. @@ -803,12 +808,12 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Cover! - Titelseite! + Cover! CRC Error - CRC Fehler + CRC-Fehler @@ -827,7 +832,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - Loading...please wait! + Loading... please wait! Ladevorgang... Bitte warten! @@ -861,12 +866,12 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Roulette look - Zufalls-Ansicht + Roulette-Ansicht Show advanced settings - Zeige erweiterte Einstellungen + Erweiterte Einstellungen anzeigen @@ -876,42 +881,42 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. View angle - Anzeige-Winkel + Blickwinkel Position - Lage + Position Cover gap - Titelbild-Abstand + Cover-Abstand Central gap - Mittiger Abstand + Mittlerer Abstand Zoom - Vergrößern + Zoom Y offset - Y-Anpassung + Y-Versatz Z offset - Z-Anpassung + Z-Versatz Cover Angle - Titelbild Ansichtswinkel + Cover-Winkel @@ -940,8 +945,8 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Benutze VSync (verbessert die Bildqualität im Vollanzeigemodus, schlechtere Leistung) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + VSync verwenden (verbessert die Bildqualität im Vollbildmodus, schlechtere Leistung) @@ -964,7 +969,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. New instance - Neuer Fall + Neue Instanz @@ -984,7 +989,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Open the latest comic opened in the previous reading session - Öffne den neuesten Comic deiner letzten Sitzung + Den zuletzt geöffneten Comic der vorherigen Lesesitzung öffnen @@ -994,7 +999,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Clear open recent list - Lösche Liste zuletzt geöffneter Elemente + Liste der zuletzt geöffneten Elemente löschen @@ -1003,35 +1008,35 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - - - + + + Save current page Aktuelle Seite speichern - - - - + + + + Extract page(s) - + Seite(n) extrahieren Extract page(s) from the original source - + Seite(n) aus der Originalquelle extrahieren Previous Comic - Voheriger Comic + Vorheriger Comic - - + + Open previous comic Vorherigen Comic öffnen @@ -1042,8 +1047,8 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - - + + Open next comic Nächsten Comic öffnen @@ -1054,20 +1059,20 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - - + + Go to previous page Zur vorherigen Seite gehen &Next - &Nächstes + &Nächste - - + + Go to next page Zur nächsten Seite gehen @@ -1089,12 +1094,12 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Fit image to width - Bildbreite anpassen + Bild an Breite anpassen Show full size - Vollansicht anzeigen + Originalgröße anzeigen @@ -1109,7 +1114,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Switch to continuous scroll mode - Wechseln Sie in den kontinuierlichen Bildlaufmodus + In den kontinuierlichen Scrollmodus wechseln @@ -1124,12 +1129,12 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Zoom+ - Vergr??ern+ + Zoom+ Zoom- - Verkleinern- + Zoom- @@ -1168,8 +1173,8 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. - Go to page ... - Gehe zu Seite ... + Go to page... + Gehe zu Seite... @@ -1179,7 +1184,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. YACReader options - YACReader Optionen + YACReader-Optionen @@ -1190,17 +1195,19 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Help, About YACReader - Hilfe, über YACReader + Hilfe, Über YACReader + + Magnifying glass - Vergößerungsglas + Vergrößerungsglas Switch Magnifying glass - Vergrößerungsglas wechseln + Vergrößerungsglas umschalten @@ -1245,7 +1252,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Close - Schliessen + Schließen @@ -1260,7 +1267,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Edit shortcuts - Kürzel ändern + Kürzel bearbeiten @@ -1281,7 +1288,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Edit - Ändern + Bearbeiten @@ -1291,7 +1298,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Go - Los + Gehe zu @@ -1299,222 +1306,217 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts.Fenster - + Open Comic Comic öffnen - + Comic files Comic-Dateien - + Open folder Ordner öffnen - - + + Comics - Comichefte + Comics - + Toggle fullscreen mode Vollbild-Modus umschalten - + Hide/show toolbar - Symbolleiste anzeigen/verstecken + Symbolleiste ein-/ausblenden - - + + General Allgemein - + Size up magnifying glass Vergrößerungsglas vergrößern - + Size down magnifying glass Vergrößerungsglas verkleinern - + Zoom in magnifying glass - Vergrößerungsglas reinzoomen + Zoom des Vergrößerungsglases erhöhen - + Zoom out magnifying glass - Vergrößerungsglas rauszoomen + Zoom des Vergrößerungsglases verringern - + Reset magnifying glass - Lupe zurücksetzen + Vergrößerungsglas zurücksetzen - - - Magnifiying glass - Vergrößerungsglas - - - + Toggle between fit to width and fit to height - Zwischen Anpassung an Seite und Höhe wechseln + Zwischen Anpassung an Breite und Höhe wechseln - - - Page adjustement + + + Page adjustment + Page adjustement Seitenanpassung - + The current page is not ready. Please try again later. Die aktuelle Seite ist noch nicht bereit. Bitte versuchen Sie es später erneut. - + Overwrite file? - + Datei überschreiben? - + The file already exists. Do you want to overwrite it? - + Die Datei existiert bereits. Möchten Sie sie überschreiben? - + The current page could not be saved. Die aktuelle Seite konnte nicht gespeichert werden. - + The selected pages are not ready. Please try again later. Die ausgewählten Seiten sind noch nicht bereit. Bitte versuchen Sie es später erneut. - + The current page could not be extracted. - + Die aktuelle Seite konnte nicht extrahiert werden. - + Overwrite files? - + Dateien überschreiben? - + Some files already exist. Do you want to overwrite them? - + Einige Dateien existieren bereits. Möchten Sie sie überschreiben? - + Some pages could not be extracted. - + Einige Seiten konnten nicht extrahiert werden. - + Autoscroll down - Automatisches Runterscrollen + Automatisch nach unten scrollen - + Autoscroll up - Automatisches Raufscrollen + Automatisch nach oben scrollen - + Autoscroll forward, horizontal first Automatisches Vorwärtsscrollen, horizontal zuerst - + Autoscroll backward, horizontal first Automatisches Zurückscrollen, horizontal zuerst - + Autoscroll forward, vertical first Automatisches Vorwärtsscrollen, vertikal zuerst - + Autoscroll backward, vertical first Automatisches Zurückscrollen, vertikal zuerst - + Move down Nach unten - + Move up Nach oben - + Move left Nach links - + Move right Nach rechts - + Go to the first page Zur ersten Seite gehen - + Go to the last page Zur letzten Seite gehen - + Offset double page to the left - Doppelseite nach links versetzt + Doppelseite nach links versetzen - + Offset double page to the right - Doppelseite nach rechts versetzt + Doppelseite nach rechts versetzen - - + + Reading - Lesend + Lesen - + There is a new version available Neue Version verfügbar - + Do you want to download the new version? Möchten Sie die neue Version herunterladen? - + Remind me in 14 days In 14 Tagen erneut erinnern - + Not now Nicht jetzt @@ -1524,12 +1526,12 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Release notes are not available. - + Versionshinweise sind nicht verfügbar. Previous versions - + Frühere Versionen @@ -1537,11 +1539,11 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Restore to default - Ursprungszustand wiederherstellen + Standardwert wiederherstellen - + Click to overwrite Anklicken, um zu überschreiben @@ -1551,13 +1553,13 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. Restore to default - Urpsrungszustand wiederherstellen + Standardwert wiederherstellen - - - + + + Click to overwrite Anklicken, um zu überschreiben @@ -1624,7 +1626,7 @@ Wenn keiner aktiv ist, geschieht beim Drücken der Escape-Taste nichts. YACReader translator - YACReader Übersetzer + YACReader-Übersetzer diff --git a/YACReader/yacreader_en.ts b/YACReader/yacreader_en.ts index 8cc6b1a84..0abd69aba 100644 --- a/YACReader/yacreader_en.ts +++ b/YACReader/yacreader_en.ts @@ -129,8 +129,8 @@ BookmarksDialog - Lastest Page - Lastest Page + Latest Page + Latest Page @@ -166,8 +166,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. @@ -181,30 +181,30 @@ - The shortcut "%1" is already assigned to other function - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function + The shortcut "%1" is already assigned to another function FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly CRC error on page (%1): some of the pages will not be displayed correctly - + Unknown error opening the file Unknown error opening the file - + 7z not found 7z not found - - + + Format not supported Format not supported @@ -213,8 +213,8 @@ GoToDialog - Page : - Page : + Page: + Page: @@ -229,8 +229,8 @@ - Total pages : - Total pages : + Total pages: + Total pages: @@ -242,8 +242,8 @@ GoToFlowToolBar - Page : - Page : + Page: + Page: @@ -266,7 +266,7 @@ Changelog - + Changelog @@ -327,7 +327,7 @@ Lanczos (better quality) - + Restart is needed Restart is needed @@ -339,17 +339,17 @@ Language - + Language Application language - + Application language System default - + System default @@ -383,8 +383,8 @@ - Scroll behaviour - Scroll behaviour + Scroll behavior + Scroll behavior @@ -418,8 +418,8 @@ - Click left or right half of the screen to turn pages. - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. @@ -517,12 +517,17 @@ If none is active, Escape does nothing. Show book fold shadow - + + Bicubic + + + + General General - + Appearance Appearance @@ -532,22 +537,22 @@ If none is active, Escape does nothing. Clear - + Page Flow Page Flow - + Image adjustment Image adjustment - + Options Options - + Comics directory Comics directory @@ -565,12 +570,12 @@ If none is active, Escape does nothing. unable to load 7z lib from ./utils - + Select custom cover Select custom cover - + Images (%1) Images (%1) @@ -787,8 +792,8 @@ If none is active, Escape does nothing. - Press 'O' to open comic. - Press 'O' to open comic. + Press 'O' to open a comic. + Press 'O' to open a comic. @@ -812,8 +817,8 @@ If none is active, Escape does nothing. - Loading...please wait! - Loading...please wait! + Loading... please wait! + Loading... please wait! @@ -940,8 +945,8 @@ If none is active, Escape does nothing. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Use VSync (improve the image quality in fullscreen mode, worse performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) @@ -1003,25 +1008,25 @@ If none is active, Escape does nothing. - - - + + + Save current page Save current page - - - - + + + + Extract page(s) - + Extract page(s) Extract page(s) from the original source - + Extract page(s) from the original source @@ -1030,8 +1035,8 @@ If none is active, Escape does nothing. - - + + Open previous comic Open previous comic @@ -1042,8 +1047,8 @@ If none is active, Escape does nothing. - - + + Open next comic Open next comic @@ -1054,8 +1059,8 @@ If none is active, Escape does nothing. - - + + Go to previous page Go to previous page @@ -1066,8 +1071,8 @@ If none is active, Escape does nothing. - - + + Go to next page Go to next page @@ -1168,8 +1173,8 @@ If none is active, Escape does nothing. - Go to page ... - Go to page ... + Go to page... + Go to page... @@ -1194,6 +1199,8 @@ If none is active, Escape does nothing. + + Magnifying glass Magnifying glass @@ -1299,222 +1306,217 @@ If none is active, Escape does nothing. Window - + Open Comic Open Comic - + Comic files Comic files - + Open folder Open folder - - + + Comics Comics - + Toggle fullscreen mode Toggle fullscreen mode - + Hide/show toolbar Hide/show toolbar - - + + General General - + Size up magnifying glass Size up magnifying glass - + Size down magnifying glass Size down magnifying glass - + Zoom in magnifying glass Zoom in magnifying glass - + Zoom out magnifying glass Zoom out magnifying glass - + Reset magnifying glass Reset magnifying glass - - - Magnifiying glass - Magnifiying glass - - - + Toggle between fit to width and fit to height Toggle between fit to width and fit to height - - - Page adjustement - Page adjustement + + + Page adjustment + Page adjustement + Page adjustment - + The current page is not ready. Please try again later. The current page is not ready. Please try again later. - + Overwrite file? - + Overwrite file? - + The file already exists. Do you want to overwrite it? - + The file already exists. Do you want to overwrite it? - + The current page could not be saved. The current page could not be saved. - + The selected pages are not ready. Please try again later. The selected pages are not ready. Please try again later. - + The current page could not be extracted. - + The current page could not be extracted. - + Overwrite files? - + Overwrite files? - + Some files already exist. Do you want to overwrite them? - + Some files already exist. Do you want to overwrite them? - + Some pages could not be extracted. - + Some pages could not be extracted. - + Autoscroll down Autoscroll down - + Autoscroll up Autoscroll up - + Autoscroll forward, horizontal first Autoscroll forward, horizontal first - + Autoscroll backward, horizontal first Autoscroll backward, horizontal first - + Autoscroll forward, vertical first Autoscroll forward, vertical first - + Autoscroll backward, vertical first Autoscroll backward, vertical first - + Move down Move down - + Move up Move up - + Move left Move left - + Move right Move right - + Go to the first page Go to the first page - + Go to the last page Go to the last page - + Offset double page to the left Offset double page to the left - + Offset double page to the right Offset double page to the right - - + + Reading Reading - + There is a new version available There is a new version available - + Do you want to download the new version? Do you want to download the new version? - + Remind me in 14 days Remind me in 14 days - + Not now Not now @@ -1524,19 +1526,19 @@ If none is active, Escape does nothing. Release notes are not available. - + Release notes are not available. Previous versions - + Previous versions YACReaderFieldEdit - + Click to overwrite Click to overwrite @@ -1550,9 +1552,9 @@ If none is active, Escape does nothing. YACReaderFieldPlainTextEdit - - - + + + Click to overwrite Click to overwrite diff --git a/YACReader/yacreader_es.ts b/YACReader/yacreader_es.ts index 4c0af8bb6..c9f41ae8a 100644 --- a/YACReader/yacreader_es.ts +++ b/YACReader/yacreader_es.ts @@ -24,7 +24,7 @@ Light - Luz + Claro @@ -145,7 +145,7 @@ - Lastest Page + Latest Page Última página @@ -166,45 +166,45 @@ - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. Para cambiar un atajo, haz doble clic en la combinación de teclas y escribe las nuevas teclas. Shortcuts settings - Configuración de accesos directos + Configuración de atajos Shortcut in use - Accesos directos en uso + Atajo en uso - The shortcut "%1" is already assigned to other function - El acceso directo "%1" ya está asignado a otra función + The shortcut "%1" is already assigned to another function + El atajo "%1" ya está asignado a otra función FileComic - - + + Format not supported Formato no soportado - + 7z not found 7z no encontrado - + Unknown error opening the file - Error desconocido abriendo el archivo + Error desconocido al abrir el archivo - + CRC error on page (%1): some of the pages will not be displayed correctly Error CRC en la página (%1): algunas de las páginas no se mostrarán correctamente @@ -224,8 +224,8 @@ - Total pages : - Páginas totales : + Total pages: + Páginas totales: @@ -234,16 +234,16 @@ - Page : - Página : + Page: + Página: GoToFlowToolBar - Page : - Página : + Page: + Página: @@ -256,12 +256,12 @@ System info - Información de sistema + Información del sistema Changelog - + Registro de cambios @@ -317,7 +317,12 @@ Lanczos (mejor calidad) - + + Bicubic + + + + Image adjustment Ajustes de imagen @@ -342,12 +347,12 @@ Contraste - + Appearance Apariencia - + Options Opciones @@ -372,9 +377,9 @@ Limpiar - + Comics directory - Directorio de cómics + Carpeta de cómics @@ -382,14 +387,14 @@ Color de fondo - + Page Flow Flujo de página - + General - Opciones generales + General @@ -397,7 +402,7 @@ Brillo - + Restart is needed Es necesario reiniciar @@ -438,7 +443,7 @@ - Scroll behaviour + Scroll behavior Comportamiento del scroll @@ -473,7 +478,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Hacer clic en la mitad izquierda o derecha de la pantalla para cambiar de página. @@ -557,20 +562,20 @@ Si ninguno está activo, la tecla Esc no hace nada. 7z lib not found - 7z lib no encontrado + Biblioteca 7z no encontrada unable to load 7z lib from ./utils - imposible cargar 7z lib de ./utils + no se puede cargar la biblioteca 7z desde ./utils - + Select custom cover Seleccionar portada personalizada - + Images (%1) Imágenes (%1) @@ -605,7 +610,7 @@ Si ninguno está activo, la tecla Esc no hace nada. i - ? + i @@ -630,7 +635,7 @@ Si ninguno está activo, la tecla Esc no hace nada. Light - Luz + Claro @@ -640,7 +645,7 @@ Si ninguno está activo, la tecla Esc no hace nada. ID: - IDENTIFICACIÓN: + ID: @@ -700,7 +705,7 @@ Si ninguno está activo, la tecla Esc no hace nada. true - verdadero + true @@ -708,7 +713,7 @@ Si ninguno está activo, la tecla Esc no hace nada. false - falso + false @@ -792,8 +797,8 @@ Si ninguno está activo, la tecla Esc no hace nada. - Press 'O' to open comic. - Pulsa 'O' para abrir un fichero. + Press 'O' to open a comic. + Pulsa 'O' para abrir un cómic. @@ -827,8 +832,8 @@ Si ninguno está activo, la tecla Esc no hace nada. - Loading...please wait! - Cargando...espere, por favor! + Loading... please wait! + Cargando... ¡espera, por favor! @@ -866,7 +871,7 @@ Si ninguno está activo, la tecla Esc no hace nada. Show advanced settings - Opciones avanzadas + Mostrar opciones avanzadas @@ -896,7 +901,7 @@ Si ninguno está activo, la tecla Esc no hace nada. Zoom - Ampliaci?n + Zoom @@ -936,11 +941,11 @@ Si ninguno está activo, la tecla Esc no hace nada. High Performance - Alto rendimiento + Rendimiento alto - Use VSync (improve the image quality in fullscreen mode, worse performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) Utilizar VSync (mejora la calidad de imagen en pantalla completa, peor rendimiento) @@ -1003,25 +1008,25 @@ Si ninguno está activo, la tecla Esc no hace nada. - - - + + + Save current page Guardar la página actual - - - - + + + + Extract page(s) - + Extraer página(s) Extract page(s) from the original source - + Extraer página(s) del origen original @@ -1030,20 +1035,20 @@ Si ninguno está activo, la tecla Esc no hace nada. - - + + Open previous comic Abrir cómic anterior Next Comic - Siguiente Cómic + Siguiente cómic - - + + Open next comic Abrir siguiente cómic @@ -1054,8 +1059,8 @@ Si ninguno está activo, la tecla Esc no hace nada. - - + + Go to previous page Ir a la página anterior @@ -1066,8 +1071,8 @@ Si ninguno está activo, la tecla Esc no hace nada. - - + + Go to next page Ir a la página siguiente @@ -1124,12 +1129,12 @@ Si ninguno está activo, la tecla Esc no hace nada. Zoom+ - Ampliar+ + Zoom+ Zoom- - Reducir + Zoom- @@ -1144,7 +1149,7 @@ Si ninguno está activo, la tecla Esc no hace nada. Double page mode - Modo a doble página + Modo de doble página @@ -1154,12 +1159,12 @@ Si ninguno está activo, la tecla Esc no hace nada. Double page manga mode - Modo de manga de página doble + Modo manga de doble página Reverse reading order in double page mode - Invertir el orden de lectura en modo de página doble + Invertir el orden de lectura en modo de doble página @@ -1168,7 +1173,7 @@ Si ninguno está activo, la tecla Esc no hace nada. - Go to page ... + Go to page... Ir a página... @@ -1190,17 +1195,19 @@ Si ninguno está activo, la tecla Esc no hace nada. Help, About YACReader - Ayuda, Sobre YACReader + Ayuda, Acerca de YACReader + + Magnifying glass Lupa Switch Magnifying glass - Lupa On/Off + Alternar lupa @@ -1260,7 +1267,7 @@ Si ninguno está activo, la tecla Esc no hace nada. Edit shortcuts - Editar accesos directos + Editar atajos @@ -1299,222 +1306,217 @@ Si ninguno está activo, la tecla Esc no hace nada. Ventana - + Open Comic Abrir cómic - + Comic files Archivos de cómic - + Open folder Abrir carpeta - - + + Comics Cómics - + Toggle fullscreen mode Alternar modo de pantalla completa - + Hide/show toolbar Ocultar/mostrar barra de herramientas - - + + General - Opciones generales + General - + Size up magnifying glass Aumentar tamaño de la lupa - + Size down magnifying glass Disminuir tamaño de lupa - + Zoom in magnifying glass - Incrementar el aumento de la lupa + Aumentar el zoom de la lupa - + Zoom out magnifying glass - Reducir el aumento de la lupa + Reducir el zoom de la lupa - + Reset magnifying glass - Resetear lupa + Restablecer lupa - - - Magnifiying glass - Lupa - - - + Toggle between fit to width and fit to height Alternar entre ajuste al ancho y ajuste al alto - - - Page adjustement + + + Page adjustment + Page adjustement Ajuste de página - + The current page is not ready. Please try again later. - La página actual aún no está lista. Por favor, inténtelo de nuevo más tarde. + La página actual aún no está lista. Por favor, inténtalo de nuevo más tarde. - + Overwrite file? - + ¿Sobrescribir el archivo? - + The file already exists. Do you want to overwrite it? - + El archivo ya existe. ¿Quieres sobrescribirlo? - + The current page could not be saved. No se ha podido guardar la página actual. - + The selected pages are not ready. Please try again later. - Las páginas seleccionadas aún no están listas. Por favor, inténtelo de nuevo más tarde. + Las páginas seleccionadas aún no están listas. Por favor, inténtalo de nuevo más tarde. - + The current page could not be extracted. - + No se ha podido extraer la página actual. - + Overwrite files? - + ¿Sobrescribir los archivos? - + Some files already exist. Do you want to overwrite them? - + Algunos archivos ya existen. ¿Quieres sobrescribirlos? - + Some pages could not be extracted. - + No se han podido extraer algunas páginas. - + Autoscroll down Desplazamiento automático hacia abajo - + Autoscroll up Desplazamiento automático hacia arriba - + Autoscroll forward, horizontal first Desplazamiento automático hacia adelante, primero horizontal - + Autoscroll backward, horizontal first Desplazamiento automático hacia atrás, primero horizontal - + Autoscroll forward, vertical first Desplazamiento automático hacia adelante, primero vertical - + Autoscroll backward, vertical first Desplazamiento automático hacia atrás, primero vertical - + Move down Mover abajo - + Move up Mover arriba - + Move left Mover a la izquierda - + Move right Mover a la derecha - + Go to the first page Ir a la primera página - + Go to the last page Ir a la última página - + Offset double page to the left - Mover una página a la izquierda + Desplazar la doble página a la izquierda - + Offset double page to the right - Mover una página a la derecha + Desplazar la doble página a la derecha - - + + Reading - Leyendo + Lectura - + There is a new version available Hay una nueva versión disponible - + Do you want to download the new version? - ¿Desea descargar la nueva versión? + ¿Quieres descargar la nueva versión? - + Remind me in 14 days Recordar en 14 días - + Not now Ahora no @@ -1524,12 +1526,12 @@ Si ninguno está activo, la tecla Esc no hace nada. Release notes are not available. - + Las notas de la versión no están disponibles. Previous versions - + Versiones anteriores @@ -1541,7 +1543,7 @@ Si ninguno está activo, la tecla Esc no hace nada. - + Click to overwrite Clic para sobrescribir @@ -1555,9 +1557,9 @@ Si ninguno está activo, la tecla Esc no hace nada. - - - + + + Click to overwrite Clic para sobrescribir @@ -1587,12 +1589,12 @@ Si ninguno está activo, la tecla Esc no hace nada. Edit shortcuts - Editar accesos directos + Editar atajos Shortcuts - Accesos directos + Atajos diff --git a/YACReader/yacreader_fr.ts b/YACReader/yacreader_fr.ts index f6f132b24..a3cfd07c6 100644 --- a/YACReader/yacreader_fr.ts +++ b/YACReader/yacreader_fr.ts @@ -6,7 +6,7 @@ None - Rien + Aucun @@ -24,7 +24,7 @@ Light - Lumière + Clair @@ -34,7 +34,7 @@ Custom - Coutume + Personnalisé @@ -49,17 +49,17 @@ Light: - Lumière: + Clair : Dark: - Sombre: + Sombre : Custom: - Personnalisation: + Personnalisé : @@ -89,7 +89,7 @@ The current theme JSON could not be loaded. - Le thème actuel JSON n'a pas pu être chargé. + Le JSON du thème actuel n'a pas pu être chargé. @@ -145,8 +145,8 @@ - Lastest Page - Aller à la dernière page + Latest Page + Dernière page @@ -176,37 +176,37 @@ - The shortcut "%1" is already assigned to other function - Le raccourci "%1" est déjà affecté à une autre fonction + The shortcut "%1" is already assigned to another function + Le raccourci « %1 » est déjà affecté à une autre fonction - To change a shortcut, double click in the key combination and type the new keys. - Pour modifier un raccourci, double-cliquez sur la combinaison de touches et tapez les nouvelles clés. + To change a shortcut, double-click the key combination and type the new keys. + Pour modifier un raccourci, double-cliquez sur la combinaison de touches et saisissez les nouvelles touches. FileComic - - + + Format not supported Format non supporté - + 7z not found 7z introuvable - + Unknown error opening the file Erreur inconnue lors de l'ouverture du fichier - + CRC error on page (%1): some of the pages will not be displayed correctly - Erreur CRC sur la page (%1): certaines pages ne s'afficheront pas correctement + Erreur CRC sur la page (%1) : certaines pages ne s'afficheront pas correctement @@ -224,8 +224,8 @@ - Total pages : - Nombre de pages : + Total pages: + Nombre de pages : @@ -234,16 +234,16 @@ - Page : - Feuille : + Page: + Page : GoToFlowToolBar - Page : - Feuille : + Page: + Page : @@ -261,12 +261,12 @@ Changelog - + Journal des modifications About - A propos + À propos @@ -274,12 +274,12 @@ Gamma - Valeur gamma + Gamma Reset - Remise à zéro + Réinitialiser @@ -287,14 +287,14 @@ Chemin de mes bandes dessinées - + Image adjustment Ajustement de l'image "Go to flow" size - Taille de "Aller à Comic Flow" + Taille de « Aller à Comic Flow » @@ -304,7 +304,7 @@ Image options - Option de l'image + Options de l'image @@ -312,14 +312,14 @@ Contraste - + Appearance Apparence - + Options - Possibilités + Options @@ -339,12 +339,12 @@ Clear - Clair + Effacer - + Comics directory - Répertoire des bandes dessinées + Dossier des bandes dessinées @@ -354,7 +354,7 @@ Display - Afficher + Affichage @@ -384,11 +384,11 @@ Background color - Couleur d'arrière plan + Couleur d'arrière-plan - Scroll behaviour + Scroll behavior Comportement de défilement @@ -399,12 +399,12 @@ Do not turn page using scroll - Ne tournez pas la page en utilisant le défilement + Ne pas tourner la page avec le défilement Use single scroll step to turn page - Utilisez une seule étape de défilement pour tourner la page + Utiliser une seule étape de défilement pour tourner la page @@ -414,7 +414,7 @@ Only Back/Forward buttons can turn pages - Seuls les boutons Précédent/Avant peuvent tourner les pages + Seuls les boutons Précédent/Suivant peuvent tourner les pages @@ -423,7 +423,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Cliquez sur la moitié gauche ou droite de l'écran pour tourner les pages. @@ -444,7 +444,7 @@ Escape closes the reader, even while a mode is active. - La touche Échap ferme le lecteur, même lorsqu’un mode est actif. + La touche Échap ferme le lecteur, même lorsqu'un mode est actif. @@ -457,19 +457,19 @@ If none is active, Escape does nothing. Tooltip listing the order in which modes are cancelled. Only the first active mode in the list is cancelled per Escape keypress. - La touche Échap annule le premier des modes actifs suivants : + La touche Échap annule le premier des modes actifs suivants : 1. Loupe 2. Dictionnaire 3. Barre Aller à la page 4. Plein écran -Si aucun n’est actif, la touche Échap ne fait rien. +Si aucun n'est actif, la touche Échap ne fait rien. Use software-rendered "go to flow" (requires restart) - Utiliser "Aller à Comic Flow" avec rendu logiciel (redémarrage requis) + Utiliser « Aller à Comic Flow » avec rendu logiciel (redémarrage requis) @@ -479,12 +479,12 @@ Si aucun n’est actif, la touche Échap ne fait rien. Disable mouse over activation - Désactiver la souris sur l'activation + Désactiver l'activation au survol de la souris Show book fold shadow - Afficher l’ombre du pli du livre + Afficher l'ombre du pli du livre @@ -512,12 +512,17 @@ Si aucun n’est actif, la touche Échap ne fait rien. Lanczos (meilleure qualité) - + + Bicubic + + + + Page Flow Flux des pages - + General Général @@ -527,7 +532,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Luminosité - + Restart is needed Redémarrage nécessaire @@ -557,22 +562,22 @@ Si aucun n’est actif, la touche Échap ne fait rien. 7z lib not found - lib 7z introuvable + Bibliothèque 7z introuvable unable to load 7z lib from ./utils - impossible de charger 7z depuis ./utils + impossible de charger la bibliothèque 7z depuis ./utils - + Select custom cover Sélectionnez une couverture personnalisée - + Images (%1) - Illustrations (%1) + Images (%1) @@ -605,7 +610,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. i - je + i @@ -620,7 +625,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Maintenez enfoncé pour faire clignoter la valeur sélectionnée dans l'interface utilisateur (magenta / basculé / 0↔10). Les versions restaurent l'original. + Maintenez enfoncé pour faire clignoter la valeur sélectionnée dans l'interface (magenta / basculé / 0↔10). Le relâchement restaure l'original. @@ -630,7 +635,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Light - Lumière + Clair @@ -640,7 +645,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. ID: - IDENTIFIANT: + ID : @@ -650,7 +655,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Variant: - Variante: + Variante : @@ -670,7 +675,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Save and apply - Enregistrer et postuler + Enregistrer et appliquer @@ -700,7 +705,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. true - vrai + true @@ -708,7 +713,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. false - FAUX + false @@ -779,7 +784,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Expected a JSON object. - Attendu un objet JSON. + Un objet JSON était attendu. @@ -787,13 +792,13 @@ Si aucun n’est actif, la touche Échap ne fait rien. Page not available! - Page non disponible ! + Page non disponible ! - Press 'O' to open comic. - Appuyez sur "O" pour ouvrir une bande dessinée. + Press 'O' to open a comic. + Appuyez sur « O » pour ouvrir une bande dessinée. @@ -803,7 +808,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Cover! - Couverture! + Couverture ! @@ -823,12 +828,12 @@ Si aucun n’est actif, la touche Échap ne fait rien. Last page! - Dernière page! + Dernière page ! - Loading...please wait! - Chargement... Patientez + Loading... please wait! + Chargement... Veuillez patienter ! @@ -836,7 +841,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Presets: - Réglages: + Réglages : @@ -871,7 +876,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Custom: - Personnalisation: + Personnalisé : @@ -896,17 +901,17 @@ Si aucun n’est actif, la touche Échap ne fait rien. Zoom - Agrandissement + Zoom Y offset - Axe Y + Décalage Y Z offset - Axe Z + Décalage Z @@ -926,7 +931,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Max angle - Angle Maximum + Angle maximum @@ -940,13 +945,13 @@ Si aucun n’est actif, la touche Échap ne fait rien. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Utiliser VSync (Améliore la qualité d'image en mode plein écran, ralentit la performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Utiliser VSync (améliore la qualité d'image en plein écran, diminue les performances) Performance: - Performance : + Performance : @@ -989,7 +994,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Clear - Clair + Effacer @@ -999,29 +1004,29 @@ Si aucun n’est actif, la touche Échap ne fait rien. Save - Sauvegarder + Enregistrer - - - + + + Save current page - Sauvegarder la page actuelle + Enregistrer la page actuelle - - - - + + + + Extract page(s) - + Extraire la ou les pages Extract page(s) from the original source - + Extraire la ou les pages depuis la source d'origine @@ -1030,10 +1035,10 @@ Si aucun n’est actif, la touche Échap ne fait rien. - - + + Open previous comic - Ouvrir la bande dessiné précédente + Ouvrir la bande dessinée précédente @@ -1042,8 +1047,8 @@ Si aucun n’est actif, la touche Échap ne fait rien. - - + + Open next comic Ouvrir la bande dessinée suivante @@ -1054,8 +1059,8 @@ Si aucun n’est actif, la touche Échap ne fait rien. - - + + Go to previous page Aller à la page précédente @@ -1066,8 +1071,8 @@ Si aucun n’est actif, la touche Échap ne fait rien. - - + + Go to next page Aller à la page suivante @@ -1094,7 +1099,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Show full size - Plein écran + Afficher en taille réelle @@ -1124,12 +1129,12 @@ Si aucun n’est actif, la touche Échap ne fait rien. Zoom+ - Agrandir + Zoom+ Zoom- - R?duire + Zoom- @@ -1159,7 +1164,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Reverse reading order in double page mode - Ordre de lecture inversée en mode double page + Ordre de lecture inversé en mode double page @@ -1168,13 +1173,13 @@ Si aucun n’est actif, la touche Échap ne fait rien. - Go to page ... - Aller à la page ... + Go to page... + Aller à la page... Options - Possibilités + Options @@ -1190,17 +1195,19 @@ Si aucun n’est actif, la touche Échap ne fait rien. Help, About YACReader - Aide, à propos de YACReader + Aide, À propos de YACReader + + Magnifying glass Loupe Switch Magnifying glass - Utiliser la loupe + Activer/désactiver la loupe @@ -1240,7 +1247,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Escape key: quit, or cancel the active mode - Touche Échap : quitter ou annuler le mode actif + Touche Échap : quitter ou annuler le mode actif @@ -1255,7 +1262,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Show go to flow - Afficher "Aller à Comic Flow" + Afficher « Aller à Comic Flow » @@ -1271,7 +1278,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Open recent - Ouvrir récent + Récemment ouverts @@ -1281,7 +1288,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Edit - Editer + Modifier @@ -1299,222 +1306,217 @@ Si aucun n’est actif, la touche Échap ne fait rien. Fenêtre - + Open Comic Ouvrir la bande dessinée - + Comic files - Bande dessinée + Fichiers de bandes dessinées - + Open folder - Ouvirir le dossier + Ouvrir le dossier - - + + Comics Bandes dessinées - + Toggle fullscreen mode Basculer en mode plein écran - + Hide/show toolbar Masquer / afficher la barre d'outils - - + + General Général - + Size up magnifying glass Augmenter la taille de la loupe - + Size down magnifying glass Réduire la taille de la loupe - + Zoom in magnifying glass - Zoomer + Augmenter le zoom de la loupe - + Zoom out magnifying glass - Dézoomer + Diminuer le zoom de la loupe - + Reset magnifying glass Réinitialiser la loupe - - - Magnifiying glass - Loupe - - - + Toggle between fit to width and fit to height Basculer entre adapter à la largeur et adapter à la hauteur - - - Page adjustement + + + Page adjustment + Page adjustement Ajustement de la page - + The current page is not ready. Please try again later. - La page actuelle n’est pas encore prête. Veuillez réessayer plus tard. + La page actuelle n'est pas encore prête. Veuillez réessayer plus tard. - + Overwrite file? - + Écraser le fichier ? - + The file already exists. Do you want to overwrite it? - + Le fichier existe déjà. Voulez-vous l'écraser ? - + The current page could not be saved. - La page actuelle n’a pas pu être enregistrée. + La page actuelle n'a pas pu être enregistrée. - + The selected pages are not ready. Please try again later. Les pages sélectionnées ne sont pas encore prêtes. Veuillez réessayer plus tard. - + The current page could not be extracted. - + La page actuelle n'a pas pu être extraite. - + Overwrite files? - + Écraser les fichiers ? - + Some files already exist. Do you want to overwrite them? - + Certains fichiers existent déjà. Voulez-vous les écraser ? - + Some pages could not be extracted. - + Certaines pages n'ont pas pu être extraites. - + Autoscroll down Défilement automatique vers le bas - + Autoscroll up Défilement automatique vers le haut - + Autoscroll forward, horizontal first - Défilement automatique en avant, horizontal + Défilement automatique en avant, horizontal en premier - + Autoscroll backward, horizontal first - Défilement automatique en arrière horizontal + Défilement automatique en arrière, horizontal en premier - + Autoscroll forward, vertical first - Défilement automatique en avant, vertical + Défilement automatique en avant, vertical en premier - + Autoscroll backward, vertical first - Défilement automatique en arrière, verticak + Défilement automatique en arrière, vertical en premier - + Move down Descendre - + Move up Monter - + Move left Déplacer à gauche - + Move right Déplacer à droite - + Go to the first page Aller à la première page - + Go to the last page Aller à la dernière page - + Offset double page to the left Double page décalée vers la gauche - + Offset double page to the right Double page décalée à droite - - + + Reading Lecture - + There is a new version available Une nouvelle version est disponible - + Do you want to download the new version? - Voulez-vous télécharger la nouvelle version? + Voulez-vous télécharger la nouvelle version ? - + Remind me in 14 days Rappelez-moi dans 14 jours - + Not now Pas maintenant @@ -1524,12 +1526,12 @@ Si aucun n’est actif, la touche Échap ne fait rien. Release notes are not available. - + Les notes de version ne sont pas disponibles. Previous versions - + Versions précédentes @@ -1541,7 +1543,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. - + Click to overwrite Cliquez pour remplacer @@ -1555,9 +1557,9 @@ Si aucun n’est actif, la touche Échap ne fait rien. - - - + + + Click to overwrite Cliquez pour remplacer @@ -1567,7 +1569,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Save - Sauvegarder + Enregistrer @@ -1587,7 +1589,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Customize the keyboard shortcuts used by the application. - Personnalisez les raccourcis clavier utilisés par l’application. + Personnalisez les raccourcis clavier utilisés par l'application. @@ -1600,7 +1602,7 @@ Si aucun n’est actif, la touche Échap ne fait rien. Reset - Remise à zéro + Réinitialiser diff --git a/YACReader/yacreader_it.ts b/YACReader/yacreader_it.ts index 9193c5ba3..7984c1737 100644 --- a/YACReader/yacreader_it.ts +++ b/YACReader/yacreader_it.ts @@ -24,22 +24,22 @@ Light - Luce + Chiaro Dark - Buio + Scuro Custom - Costume + Personalizzato Remove - Rimuovere + Rimuovi @@ -49,17 +49,17 @@ Light: - Leggero: + Chiaro: Dark: - Buio: + Scuro: Custom: - Personalizzazione: + Personalizzato: @@ -74,7 +74,7 @@ Theme editor - Redattore del tema + Editor del tema @@ -89,7 +89,7 @@ The current theme JSON could not be loaded. - Impossibile caricare il tema corrente JSON. + Impossibile caricare il JSON del tema corrente. @@ -145,7 +145,7 @@ - Lastest Page + Latest Page Ultima Pagina @@ -167,44 +167,44 @@ Restore defaults - Resetta al default + Ripristina predefiniti Shortcuts settings - impostazione scorciatoie + Impostazioni scorciatoie - The shortcut "%1" is already assigned to other function - La scorciatoia "%1" è già assegnata ad un'altra funzione + The shortcut "%1" is already assigned to another function + La scorciatoia "%1" è già assegnata a un'altra funzione - To change a shortcut, double click in the key combination and type the new keys. - Per cambiare una scorciatoia doppio click sulla combinazione tasti e digita la nuova combinazione. + To change a shortcut, double-click the key combination and type the new keys. + Per cambiare una scorciatoia, fai doppio clic sulla combinazione di tasti e digita la nuova combinazione. FileComic - - + + Format not supported Formato non supportato - + 7z not found 7z non trovato - + Unknown error opening the file Errore sconosciuto aprendo il file - + CRC error on page (%1): some of the pages will not be displayed correctly Errore CRC alla pagina (%1): alcune pagine non saranno visualizzate correttamente @@ -224,7 +224,7 @@ - Total pages : + Total pages: Pagine totali: @@ -234,7 +234,7 @@ - Page : + Page: Pagina: @@ -242,7 +242,7 @@ GoToFlowToolBar - Page : + Page: Pagina: @@ -261,7 +261,7 @@ Changelog - + Registro modifiche @@ -274,7 +274,7 @@ Gamma - Valore gamma + Gamma @@ -287,7 +287,7 @@ Percorso dei miei fumetti - + Image adjustment Correzioni immagine @@ -304,7 +304,7 @@ Image options - Opzione immagine + Opzioni immagine @@ -312,12 +312,12 @@ Contrasto - + Appearance Aspetto - + Options Opzioni @@ -342,14 +342,14 @@ Cancella - + Comics directory - Cartella Fumetti + Cartella fumetti Quick Navigation Mode - Modo navigazione rapida + Modalità navigazione rapida @@ -388,7 +388,7 @@ - Scroll behaviour + Scroll behavior Comportamento di scorrimento @@ -404,7 +404,7 @@ Use single scroll step to turn page - Utilizzare un singolo passaggio di scorrimento per voltare pagina + Usa un singolo passaggio di scorrimento per voltare pagina @@ -419,12 +419,12 @@ Use the Left/Right buttons to turn pages. - Utilizzare i pulsanti Sinistra/Destra per girare le pagine. + Usa i pulsanti Sinistra/Destra per girare le pagine. - Click left or right half of the screen to turn pages. - Fare clic sulla metà sinistra o destra dello schermo per girare le pagine. + Click the left or right half of the screen to turn pages. + Clicca sulla metà sinistra o destra dello schermo per girare le pagine. @@ -479,12 +479,12 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Disable mouse over activation - Disabilita il mouse all'attivazione + Disabilita l'attivazione al passaggio del mouse Show book fold shadow - Mostra l’ombra della piega del libro + Mostra l'ombra della piega del libro @@ -494,7 +494,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Scaling method - Metodo di scala + Metodo di ridimensionamento @@ -512,12 +512,17 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione.Lanczos (qualità migliore) - + + Bicubic + + + + Page Flow Flusso pagine - + General Generale @@ -527,9 +532,9 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione.Luminosità - + Restart is needed - Riavvio Necessario + Riavvio necessario @@ -562,15 +567,15 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. unable to load 7z lib from ./utils - Impossibile caricare 7z da ./utils + Impossibile caricare la libreria 7z da ./utils - + Select custom cover Seleziona la copertina personalizzata - + Images (%1) Immagini (%1) @@ -590,7 +595,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Theme Editor - Redattore del tema + Editor del tema @@ -605,7 +610,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. i - io + i @@ -620,7 +625,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Tieni premuto per far lampeggiare il valore selezionato nell'interfaccia utente (magenta / alternato / 0↔10). Le versioni ripristinano l'originale. + Tieni premuto per far lampeggiare il valore selezionato nell'interfaccia (magenta / alternato / 0↔10). Al rilascio viene ripristinato l'originale. @@ -630,17 +635,17 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Light - Luce + Chiaro Dark - Buio + Scuro ID: - Identificativo: + ID: @@ -690,7 +695,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Double-click to edit color - Fare doppio clic per modificare il colore + Fai doppio clic per modificare il colore @@ -700,7 +705,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. true - VERO + true @@ -708,17 +713,17 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. false - falso + false Double-click to toggle - Fare doppio clic per attivare/disattivare + Fai doppio clic per attivare/disattivare Double-click to edit value - Fare doppio clic per modificare il valore + Fai doppio clic per modificare il valore @@ -792,13 +797,13 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - Press 'O' to open comic. - Premi "O" per aprire il fumettto. + Press 'O' to open a comic. + Premi "O" per aprire un fumetto. Error opening comic - Errore nell'apertura + Errore nell'apertura del fumetto @@ -827,8 +832,8 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - Loading...please wait! - In caricamento...Attendi! + Loading... please wait! + Caricamento... Attendi! @@ -871,7 +876,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Custom: - Personalizzazione: + Personalizzato: @@ -886,7 +891,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Cover gap - Distanza Copertine + Distanza tra le copertine @@ -896,17 +901,17 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Zoom - Ingrandimento + Zoom Y offset - Compensazione Y + Offset Y Z offset - Compensazione Z + Offset Z @@ -931,17 +936,17 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Low Performance - Prestazioni Basse + Prestazioni basse High Performance - Prestazioni Alte + Prestazioni alte - Use VSync (improve the image quality in fullscreen mode, worse performance) - Usa VSync (migliora la qualtà a tutto schermo, peggiora le prestazioni) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Usa VSync (migliora la qualità dell'immagine a schermo intero, peggiora le prestazioni) @@ -959,7 +964,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Open a comic - Apri un Fumetto + Apri un fumetto @@ -974,7 +979,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Open image folder - Apri la crettal immagini + Apri la cartella di immagini @@ -994,7 +999,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Clear open recent list - Svuota la lista degli aperti + Svuota l'elenco dei file recenti @@ -1003,37 +1008,37 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - - - + + + Save current page Salva la pagina corrente - - - - + + + + Extract page(s) - + Estrai pagina/e Extract page(s) from the original source - + Estrai pagina/e dalla sorgente originale Previous Comic - Fumetto precendente + Fumetto precedente - - + + Open previous comic - Apri il fumetto precendente + Apri il fumetto precedente @@ -1042,8 +1047,8 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - - + + Open next comic Apri il prossimo fumetto @@ -1054,8 +1059,8 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - - + + Go to previous page Vai alla pagina precedente @@ -1066,10 +1071,10 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - - + + Go to next page - Vai alla prossima Pagina + Vai alla prossima pagina @@ -1084,7 +1089,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Fit Width - Adatta Larghezza + Adatta larghezza @@ -1094,7 +1099,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Show full size - Mostra dimesioni reali + Mostra dimensioni reali @@ -1114,7 +1119,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Reset zoom - Resetta Zoom + Resetta zoom @@ -1124,12 +1129,12 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Zoom+ - Aumenta + Zoom+ Zoom- - Riduci + Zoom- @@ -1144,7 +1149,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Double page mode - Modalita doppia pagina + Modalità doppia pagina @@ -1159,7 +1164,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Reverse reading order in double page mode - Ordine lettura inverso in modo doppia pagina + Ordine di lettura inverso in modalità doppia pagina @@ -1168,8 +1173,8 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. - Go to page ... - Vai a Pagina ... + Go to page... + Vai a pagina... @@ -1190,37 +1195,39 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Help, About YACReader - Aiuto, crediti YACReader + Aiuto, informazioni su YACReader + + Magnifying glass Lente ingrandimento Switch Magnifying glass - Passa a lente ingrandimento + Attiva/Disattiva lente d'ingrandimento Set bookmark - Imposta Segnalibro + Imposta segnalibro Set a bookmark on the current page - Imposta segnalibro a pagina corrente + Imposta un segnalibro nella pagina corrente Show bookmarks - Mostra segnalibro + Mostra segnalibri Show the bookmarks of the current comic - Mostra il segnalibro del fumetto corrente + Mostra i segnalibri del fumetto corrente @@ -1260,12 +1267,12 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Edit shortcuts - Edita scorciatoie + Modifica scorciatoie &File - &Documento + &File @@ -1276,17 +1283,17 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. File - Documento + File Edit - Edita + Modifica View - Mostra + Visualizza @@ -1299,222 +1306,217 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione.Finestra - + Open Comic - Apri Fumetto + Apri fumetto - + Comic files - File Fumetto + File di fumetti - + Open folder Apri cartella - - + + Comics - Fumetto + Fumetti - + Toggle fullscreen mode Attiva/Disattiva schermo intero - + Hide/show toolbar - Mostra/Nascondi Barra strumenti + Mostra/Nascondi barra strumenti - - + + General Generale - + Size up magnifying glass - Ingrandisci lente ingrandimento + Ingrandisci la lente d'ingrandimento - + Size down magnifying glass - Riduci lente ingrandimento + Riduci la lente d'ingrandimento - + Zoom in magnifying glass - Ingrandisci in lente di ingrandimento + Aumenta lo zoom della lente - + Zoom out magnifying glass - Riduci in lente di ingrandimento + Riduci lo zoom della lente - + Reset magnifying glass Reimposta la lente d'ingrandimento - - - Magnifiying glass - Lente ingrandimento - - - + Toggle between fit to width and fit to height - Passa tra adatta in larghezza ad altezza + Passa tra adatta alla larghezza e adatta all'altezza - - - Page adjustement - Correzioni di pagna + + + Page adjustment + Page adjustement + Regolazione della pagina - + The current page is not ready. Please try again later. La pagina corrente non è ancora pronta. Riprova più tardi. - + Overwrite file? - + Sovrascrivere il file? - + The file already exists. Do you want to overwrite it? - + Il file esiste già. Vuoi sovrascriverlo? - + The current page could not be saved. Non è stato possibile salvare la pagina corrente. - + The selected pages are not ready. Please try again later. Le pagine selezionate non sono ancora pronte. Riprova più tardi. - + The current page could not be extracted. - + Non è stato possibile estrarre la pagina corrente. - + Overwrite files? - + Sovrascrivere i file? - + Some files already exist. Do you want to overwrite them? - + Alcuni file esistono già. Vuoi sovrascriverli? - + Some pages could not be extracted. - + Non è stato possibile estrarre alcune pagine. - + Autoscroll down - Autoscorri Giù + Scorrimento automatico in giù - + Autoscroll up - Autoscorri Sù + Scorrimento automatico in su - + Autoscroll forward, horizontal first - Autoscorri avanti, priorità Orizzontale + Scorrimento automatico in avanti, prima orizzontale - + Autoscroll backward, horizontal first - Autoscorri indietro, priorità Orizzontale + Scorrimento automatico indietro, prima orizzontale - + Autoscroll forward, vertical first - Autoscorri avanti, priorità Verticale + Scorrimento automatico in avanti, prima verticale - + Autoscroll backward, vertical first - Autoscorri indietro, priorità Verticale + Scorrimento automatico indietro, prima verticale - + Move down - Muovi Giù + Sposta in giù - + Move up - Muovi Sù + Sposta in su - + Move left - Muovi Sinistra + Sposta a sinistra - + Move right - Muovi Destra + Sposta a destra - + Go to the first page Vai alla pagina iniziale - + Go to the last page Vai all'ultima pagina - + Offset double page to the left Doppia pagina spostata a sinistra - + Offset double page to the right Doppia pagina spostata a destra - - + + Reading - Leggi + Lettura - + There is a new version available Nuova versione disponibile - + Do you want to download the new version? Vuoi scaricare la nuova versione? - + Remind me in 14 days Ricordamelo in 14 giorni - + Not now Non ora @@ -1524,12 +1526,12 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Release notes are not available. - + Le note di rilascio non sono disponibili. Previous versions - + Versioni precedenti @@ -1537,11 +1539,11 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Restore to default - Resetta al default + Ripristina predefinito - + Click to overwrite Clicca per sovrascrivere @@ -1551,13 +1553,13 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Restore to default - Resetta al default + Ripristina predefinito - - - + + + Click to overwrite Clicca per sovrascrivere @@ -1572,12 +1574,12 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Cancel - Cancella + Annulla Shortcuts - Scorciatoia + Scorciatoie @@ -1592,7 +1594,7 @@ Se non è attiva alcuna modalità, il tasto Esc non esegue alcuna azione. Edit shortcuts - Edita Scorciatoia + Modifica scorciatoie diff --git a/YACReader/yacreader_ko.ts b/YACReader/yacreader_ko.ts index 13dacf490..a86d11c7c 100644 --- a/YACReader/yacreader_ko.ts +++ b/YACReader/yacreader_ko.ts @@ -44,7 +44,7 @@ Remove this user-imported theme - 사용자 가져온 이 테마 제거 + 사용자가 가져온 이 테마 제거 @@ -129,7 +129,7 @@ BookmarksDialog - Lastest Page + Latest Page 마지막 페이지 @@ -166,8 +166,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - 단축키를 바꾸려면 키 조합을 더블 클릭하고 새 키를 입력하세요. + To change a shortcut, double-click the key combination and type the new keys. + 단축키를 바꾸려면 키 조합을 두 번 클릭하고 새 키를 입력하세요. @@ -181,30 +181,30 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function "%1" 단축키는 이미 다른 기능이 사용 중입니다 FileComic - + 7z not found 7z를 찾을 수 없습니다 - + CRC error on page (%1): some of the pages will not be displayed correctly %1번 페이지에서 CRC 오류 발생: 일부 페이지가 올바르게 표시되지 않을 수 있습니다 - + Unknown error opening the file 파일을 여는 중 알 수 없는 오류가 발생했습니다 - - + + Format not supported 지원하지 않는 형식입니다 @@ -213,7 +213,7 @@ GoToDialog - Page : + Page: 페이지: @@ -229,7 +229,7 @@ - Total pages : + Total pages: 전체 페이지: @@ -242,7 +242,7 @@ GoToFlowToolBar - Page : + Page: 페이지: @@ -266,7 +266,7 @@ Changelog - + 변경 로그 @@ -292,17 +292,17 @@ 지우기 - + General 일반 - + Appearance 외관 - + Options 환경설정 @@ -358,7 +358,7 @@ - Scroll behaviour + Scroll behavior 스크롤 동작 @@ -393,7 +393,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. 화면 왼쪽 또는 오른쪽 절반을 클릭하여 페이지 넘김. @@ -489,7 +489,7 @@ If none is active, Escape does nothing. Enlarge images to fit width/height - 작은 그림도 꽉차게 보기 + 이미지를 폭/높이에 맞게 확대 @@ -519,35 +519,40 @@ If none is active, Escape does nothing. Nearest (fast, low quality) - 빠른 모드 (빠름, 저화질) + 최근접 (빠름, 저화질) Bilinear - 보통 모드 (중간 품질) + Bilinear Lanczos (better quality) - 고화질 모드 (더 좋은 화질) + Lanczos (더 좋은 화질) - - Page Flow - 페이지 플로우 + + Bicubic + + Page Flow + 페이지 흐름 + + + Image adjustment 이미지 조정 - + Restart is needed 재시작이 필요합니다 - + Comics directory 만화 폴더 @@ -565,12 +570,12 @@ If none is active, Escape does nothing. ./utils에서 7z 라이브러리를 불러올 수 없습니다 - + Select custom cover 사용자 지정 표지 선택 - + Images (%1) 이미지 (%1) @@ -787,7 +792,7 @@ If none is active, Escape does nothing. - Press 'O' to open comic. + Press 'O' to open a comic. 'O'를 눌러 만화를 열어보세요. @@ -812,7 +817,7 @@ If none is active, Escape does nothing. - Loading...please wait! + Loading... please wait! 불러오는 중... 잠시 기다려주세요! @@ -896,7 +901,7 @@ If none is active, Escape does nothing. Zoom - 확대/축소 + Zoom @@ -940,8 +945,8 @@ If none is active, Escape does nothing. - Use VSync (improve the image quality in fullscreen mode, worse performance) - 수직 동기화 사용 (전체화면 결의 이미지 품질 향상, 성능 저하) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + VSync 사용 (전체 화면 모드에서 이미지 품질 향상, 성능 저하) @@ -1003,18 +1008,18 @@ If none is active, Escape does nothing. - - - + + + Save current page 현재 페이지 저장 - - - - + + + + Extract page(s) 페이지 추출 @@ -1030,8 +1035,8 @@ If none is active, Escape does nothing. - - + + Open previous comic 이전 만화 열기 @@ -1042,8 +1047,8 @@ If none is active, Escape does nothing. - - + + Open next comic 다음 만화 열기 @@ -1054,8 +1059,8 @@ If none is active, Escape does nothing. - - + + Go to previous page 이전 페이지로 이동 @@ -1066,15 +1071,15 @@ If none is active, Escape does nothing. - - + + Go to next page 다음 페이지로 이동 Fit Height - 꽉차게 보기 (높이 맞춤) + 높이 맞춤 @@ -1084,7 +1089,7 @@ If none is active, Escape does nothing. Fit Width - 꽉차게 보기 (폭 맞춤) + 폭 맞춤 @@ -1099,7 +1104,7 @@ If none is active, Escape does nothing. Fit to page - 꽉차게 보기 + 페이지에 맞춤 @@ -1114,22 +1119,22 @@ If none is active, Escape does nothing. Reset zoom - 확대/축소 초기화 + Zoom 초기화 Show zoom slider - 확대/축소 슬라이더 보기 + Zoom 슬라이더 보기 Zoom+ - 확대+ + Zoom+ Zoom- - 축소- + Zoom- @@ -1168,7 +1173,7 @@ If none is active, Escape does nothing. - Go to page ... + Go to page... 페이지로 이동... @@ -1194,6 +1199,8 @@ If none is active, Escape does nothing. + + Magnifying glass 돋보기 @@ -1299,222 +1306,217 @@ If none is active, Escape does nothing. 창 - + Open Comic 만화 열기 - + Comic files 만화 파일 - + Open folder 폴더 열기 - + The current page is not ready. Please try again later. 현재 페이지가 아직 준비되지 않았습니다. 나중에 다시 시도해 주세요. - + Overwrite file? 파일을 덮어쓰시겠습니까? - + The file already exists. Do you want to overwrite it? 파일이 이미 존재합니다. 덮어쓰시겠습니까? - + The current page could not be saved. 현재 페이지를 저장할 수 없습니다. - + The selected pages are not ready. Please try again later. 선택한 페이지가 아직 준비되지 않았습니다. 나중에 다시 시도해 주세요. - + The current page could not be extracted. 현재 페이지를 추출할 수 없습니다. - + Overwrite files? 파일을 덮어쓰시겠습니까? - + Some files already exist. Do you want to overwrite them? 일부 파일이 이미 존재합니다. 덮어쓰시겠습니까? - + Some pages could not be extracted. 일부 페이지를 추출할 수 없습니다. - - + + Comics 만화 - - + + General 일반 - - - Magnifiying glass - 돋보기 - - - - - Page adjustement + + + Page adjustment + Page adjustement 페이지 조정 - - + + Reading 읽기 - + Toggle fullscreen mode - 전체화면 전환 + 전체 화면 전환 - + Hide/show toolbar 도구 모음 표시/숨김 - + Size up magnifying glass 돋보기 크게 - + Size down magnifying glass 돋보기 작게 - + Zoom in magnifying glass - 돋보기 확대 + 돋보기 Zoom 확대 - + Zoom out magnifying glass - 돋보기 축소 + 돋보기 Zoom 축소 - + Reset magnifying glass 돋보기 초기화 - + Toggle between fit to width and fit to height 폭 맞춤 / 높이 맞춤 전환 - + Autoscroll down 아래로 자동 스크롤 - + Autoscroll up 위로 자동 스크롤 - + Autoscroll forward, horizontal first - 세로 우선으로 정방향 자동 스크롤 + 가로 우선으로 정방향 자동 스크롤 - + Autoscroll backward, horizontal first - 가로 우선으로 정방향 자동 스크롤 + 가로 우선으로 역방향 자동 스크롤 - + Autoscroll forward, vertical first - 세로 우선으로 역방향 자동 스크롤 + 세로 우선으로 정방향 자동 스크롤 - + Autoscroll backward, vertical first - 가로 우선으로 역방향 자동 스크롤 + 세로 우선으로 역방향 자동 스크롤 - + Move down 아래로 이동 - + Move up 위로 이동 - + Move left 왼쪽으로 이동 - + Move right 오른쪽으로 이동 - + Go to the first page 첫 페이지로 이동 - + Go to the last page 마지막 페이지로 이동 - + Offset double page to the left 두 페이지 왼쪽으로 이동 - + Offset double page to the right 두 페이지 오른쪽으로 이동 - + There is a new version available - 새 버전을 내려받으시겠습니까? + 새 버전이 있습니다 - + Do you want to download the new version? 새 버전을 내려받으시겠습니까? - + Remind me in 14 days 14일 후에 다시 알림 - + Not now 나중에 @@ -1536,7 +1538,7 @@ If none is active, Escape does nothing. YACReaderFieldEdit - + Click to overwrite 클릭해서 덮어쓰기 @@ -1550,9 +1552,9 @@ If none is active, Escape does nothing. YACReaderFieldPlainTextEdit - - - + + + Click to overwrite 클릭해서 덮어쓰기 diff --git a/YACReader/yacreader_local_client.cpp b/YACReader/yacreader_local_client.cpp index 6cb6cb922..4b2061976 100644 --- a/YACReader/yacreader_local_client.cpp +++ b/YACReader/yacreader_local_client.cpp @@ -28,7 +28,7 @@ void YACReaderLocalClient::readMessage() } #include -bool YACReaderLocalClient::requestComicInfo(quint64 libraryId, ComicDB &comic, QList &siblings, OpenComicSource source) +bool YACReaderLocalClient::requestComicInfo(quint64 libraryId, ComicDB &comic, QList &siblings, qint32 &position, OpenComicSource source) { localSocket->connectToServer(YACREADERLIBRARY_GUID); if (localSocket->isOpen()) { @@ -107,6 +107,7 @@ bool YACReaderLocalClient::requestComicInfo(quint64 libraryId, ComicDB &comic, Q QDataStream dataStream(data); dataStream >> comic; dataStream >> siblings; + dataStream >> position; localSocket->close(); return true; } else { diff --git a/YACReader/yacreader_local_client.h b/YACReader/yacreader_local_client.h index d4472b0b9..b23a281f4 100644 --- a/YACReader/yacreader_local_client.h +++ b/YACReader/yacreader_local_client.h @@ -18,7 +18,8 @@ class YACReaderLocalClient : public QObject void finished(); public slots: void readMessage(); - bool requestComicInfo(quint64 libraryId, ComicDB &comic, QList &siblings, YACReader::OpenComicSource source); + // position: the index of the opened comic in siblings, -1 if it is not there + bool requestComicInfo(quint64 libraryId, ComicDB &comic, QList &siblings, qint32 &position, YACReader::OpenComicSource source); bool sendComicInfo(quint64 libraryId, ComicDB &comic); bool sendComicInfo(quint64 libraryId, ComicDB &comic, qulonglong nextComicId); diff --git a/YACReader/yacreader_nl.ts b/YACReader/yacreader_nl.ts index 62a8a0761..c94e5a2ec 100644 --- a/YACReader/yacreader_nl.ts +++ b/YACReader/yacreader_nl.ts @@ -44,7 +44,7 @@ Remove this user-imported theme - Verwijder dit door de gebruiker geïmporteerde thema + Dit door de gebruiker geïmporteerde thema verwijderen @@ -141,11 +141,11 @@ Click on any image to go to the bookmark - Klik op een afbeelding om naar de bladwijzer te gaan + Klik op een afbeelding om naar de bladwijzer te gaan - Lastest Page + Latest Page Laatste Pagina @@ -154,7 +154,7 @@ Loading page %1 - Pagina laden %1 + Pagina %1 laden @@ -166,45 +166,45 @@ - To change a shortcut, double click in the key combination and type the new keys. - Om een ​​snelkoppeling te wijzigen, dubbelklikt u op de toetsencombinatie en typt u de nieuwe toetsen. + To change a shortcut, double-click the key combination and type the new keys. + Om een sneltoets te wijzigen, dubbelklikt u op de toetsencombinatie en typt u de nieuwe toetsen. Shortcuts settings - Instellingen voor snelkoppelingen + Instellingen voor sneltoetsen Shortcut in use - Snelkoppeling in gebruik + Sneltoets in gebruik - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function De sneltoets "%1" is al aan een andere functie toegewezen FileComic - + 7z not found - 7Z Archiefbestand niet gevonden + 7z niet gevonden - + CRC error on page (%1): some of the pages will not be displayed correctly CRC-fout op pagina (%1): sommige pagina's worden niet correct weergegeven - + Unknown error opening the file Onbekende fout bij het openen van het bestand - - + + Format not supported Formaat niet ondersteund @@ -214,7 +214,7 @@ Go To - Ga Naar + Ga naar @@ -224,8 +224,8 @@ - Total pages : - Totaal aantal pagina's : + Total pages: + Totaal aantal pagina's: @@ -234,16 +234,16 @@ - Page : - Pagina : + Page: + Pagina: GoToFlowToolBar - Page : - Pagina : + Page: + Pagina: @@ -261,7 +261,7 @@ Changelog - + Wijzigingslogboek @@ -274,12 +274,12 @@ Gamma - Gammawaarde + Gamma Reset - Standaardwaarden terugzetten + Resetten @@ -309,7 +309,7 @@ Bilinear - Bilineair + Bilinear @@ -317,7 +317,12 @@ Lanczos (betere kwaliteit) - + + Bicubic + + + + Image adjustment Beeldaanpassing @@ -334,20 +339,20 @@ Image options - Afbeelding opties + Afbeeldingsopties Contrast - Contrastwaarde + Contrast - + Appearance - Verschijning + Uiterlijk - + Options Opties @@ -369,12 +374,12 @@ Clear - Duidelijk + Wissen - + Comics directory - Strips map + Stripmap @@ -382,12 +387,12 @@ Achtergrondkleur - + Page Flow - Omslagbrowser + Pagina-flow - + General Algemeen @@ -397,7 +402,7 @@ Helderheid - + Restart is needed Herstart is nodig @@ -438,7 +443,7 @@ - Scroll behaviour + Scroll behavior Scrollgedrag @@ -473,7 +478,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Klik op de linker- of rechterhelft van het scherm om pagina's om te slaan. @@ -534,7 +539,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Fit options - Pas opties + Aanpassingsopties @@ -565,12 +570,12 @@ Als geen enkele modus actief is, doet de Escape-toets niets. kan 7z lib niet laden vanuit ./utils - + Select custom cover - Selecteer een aangepaste omslag + Selecteer een aangepast omslag - + Images (%1) Afbeeldingen (%1) @@ -605,7 +610,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. i - ? + i @@ -625,7 +630,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Search… - Zoekopdracht… + Zoeken… @@ -640,7 +645,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. ID: - Identiteitskaart: + ID: @@ -650,7 +655,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Variant: - Variatie: + Variant: @@ -660,7 +665,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Parameter - Instelwaarde + Parameter @@ -700,7 +705,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. true - WAAR + true @@ -708,7 +713,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. false - vals + false @@ -787,8 +792,8 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - Press 'O' to open comic. - Druk 'O' om een strip te openen. + Press 'O' to open a comic. + Druk op 'O' om een strip te openen. @@ -812,8 +817,8 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - Loading...please wait! - Inladen...even wachten! + Loading... please wait! + Inladen... even wachten! @@ -846,7 +851,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Stripe look - Brede band + Brede band @@ -886,7 +891,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Cover gap - Ruimte tss Omslag + Ruimte tussen omslagen @@ -896,22 +901,22 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Zoom - Vergroting + Zoom Y offset - Y-positie + Y-verschuiving Z offset - Z- positie + Z-verschuiving Cover Angle - Omslag hoek + Omslaghoek @@ -940,8 +945,8 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Gebruik VSync (verbetering van de beeldkwaliteit in de modus volledig scherm, slechtere prestatie) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Gebruik VSync (verbetert de beeldkwaliteit in de modus volledig scherm, maar verlaagt de prestaties) @@ -959,12 +964,12 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Open a comic - Open een strip + Een strip openen New instance - Nieuw exemplaar + Nieuwe instantie @@ -974,54 +979,54 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Open image folder - Open afbeeldings map + Afbeeldingenmap openen Open latest comic - Open de nieuwste strip + Laatst geopende strip openen Open the latest comic opened in the previous reading session - Open de nieuwste strip die in de vorige leessessie is geopend + De laatst geopende strip van de vorige leessessie openen Clear - Duidelijk + Wissen Clear open recent list - Wis geopende recente lijst + Lijst met recent geopende bestanden wissen Save - Bewaar + Opslaan - - - + + + Save current page - Bewaren huidige pagina + Huidige pagina opslaan - - - - + + + + Extract page(s) - + Pagina('s) extraheren Extract page(s) from the original source - + Pagina('s) uit de oorspronkelijke bron extraheren @@ -1030,8 +1035,8 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - - + + Open previous comic Open de vorige strip @@ -1042,8 +1047,8 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - - + + Open next comic Open volgende strip @@ -1054,8 +1059,8 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - - + + Go to previous page Ga naar de vorige pagina @@ -1066,15 +1071,15 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - - + + Go to next page Ga naar de volgende pagina Fit Height - Geschikte hoogte + Hoogte aanpassen @@ -1084,7 +1089,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Fit Width - Vensterbreedte aanpassen + Breedte aanpassen @@ -1094,7 +1099,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Show full size - Volledig Scherm + Op ware grootte tonen @@ -1124,32 +1129,32 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Zoom+ - Inzoomen + Zoom+ Zoom- - Uitzoomen + Zoom- Rotate image to the left - Links omdraaien + Afbeelding naar links draaien Rotate image to the right - Rechts omdraaien + Afbeelding naar rechts draaien Double page mode - Dubbele bladzijde modus + Modus met dubbele pagina Switch to double page mode - Naar dubbele bladzijde modus + Overschakelen naar modus met dubbele pagina @@ -1164,12 +1169,12 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Go To - Ga Naar + Ga naar - Go to page ... - Ga naar bladzijde ... + Go to page... + Ga naar pagina... @@ -1194,13 +1199,15 @@ Als geen enkele modus actief is, doet de Escape-toets niets. + + Magnifying glass Vergrootglas Switch Magnifying glass - Overschakelen naar Vergrootglas + Vergrootglas in-/uitschakelen @@ -1250,7 +1257,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Show Dictionary - Woordenlijst weergeven + Woordenboek weergeven @@ -1260,7 +1267,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Edit shortcuts - Snelkoppelingen bewerken + Sneltoetsen bewerken @@ -1291,230 +1298,225 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Go - Gaan + Ga naar Window - Raam + Venster - + Open Comic - Open een Strip + Strip openen - + Comic files - Strip bestanden + Stripbestanden - + Open folder - Open een Map + Map openen - - + + Comics Strips - + Toggle fullscreen mode - Schakel de modus Volledig scherm in + Modus Volledig scherm in-/uitschakelen - + Hide/show toolbar Werkbalk verbergen/tonen - - + + General Algemeen - + Size up magnifying glass Vergrootglas vergroten - + Size down magnifying glass Vergrootglas kleiner maken - + Zoom in magnifying glass - Zoom in vergrootglas + Zoom van vergrootglas vergroten - + Zoom out magnifying glass - Uitzoomen vergrootglas + Zoom van vergrootglas verkleinen - + Reset magnifying glass Vergrootglas opnieuw instellen - - - Magnifiying glass - Vergrootglas - - - + Toggle between fit to width and fit to height Schakel tussen Aanpassen aan breedte en Aanpassen aan hoogte - - - Page adjustement + + + Page adjustment + Page adjustement Pagina-aanpassing - + The current page is not ready. Please try again later. De huidige pagina is nog niet klaar. Probeer het later opnieuw. - + Overwrite file? - + Bestand overschrijven? - + The file already exists. Do you want to overwrite it? - + Het bestand bestaat al. Wilt u het overschrijven? - + The current page could not be saved. De huidige pagina kon niet worden opgeslagen. - + The selected pages are not ready. Please try again later. - De geselecteerde pagina’s zijn nog niet klaar. Probeer het later opnieuw. + De geselecteerde pagina's zijn nog niet klaar. Probeer het later opnieuw. - + The current page could not be extracted. - + De huidige pagina kon niet worden geëxtraheerd. - + Overwrite files? - + Bestanden overschrijven? - + Some files already exist. Do you want to overwrite them? - + Sommige bestanden bestaan al. Wilt u ze overschrijven? - + Some pages could not be extracted. - + Sommige pagina's konden niet worden geëxtraheerd. - + Autoscroll down Automatisch naar beneden scrollen - + Autoscroll up Automatisch omhoog scrollen - + Autoscroll forward, horizontal first Automatisch vooruit scrollen, eerst horizontaal - + Autoscroll backward, horizontal first Automatisch achteruit scrollen, eerst horizontaal - + Autoscroll forward, vertical first Automatisch vooruit scrollen, eerst verticaal - + Autoscroll backward, vertical first Automatisch achteruit scrollen, eerst verticaal - + Move down Ga naar beneden - + Move up Ga omhoog - + Move left Ga naar links - + Move right Ga naar rechts - + Go to the first page Ga naar de eerste pagina - + Go to the last page Ga naar de laatste pagina - + Offset double page to the left - Dubbele pagina naar links verschoven + Dubbele pagina naar links verschuiven - + Offset double page to the right - Offset dubbele pagina naar rechts + Dubbele pagina naar rechts verschuiven - - + + Reading - Lezing + Lezen - + There is a new version available Er is een nieuwe versie beschikbaar - + Do you want to download the new version? Wilt u de nieuwe versie downloaden? - + Remind me in 14 days Herinner mij er over 14 dagen aan - + Not now Niet nu @@ -1524,12 +1526,12 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Release notes are not available. - + Releaseopmerkingen zijn niet beschikbaar. Previous versions - + Vorige versies @@ -1541,7 +1543,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - + Click to overwrite Klik hier om te overschrijven @@ -1555,9 +1557,9 @@ Als geen enkele modus actief is, doet de Escape-toets niets. - - - + + + Click to overwrite Klik hier om te overschrijven @@ -1567,7 +1569,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Save - Bewaar + Opslaan @@ -1587,12 +1589,12 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Edit shortcuts - Snelkoppelingen bewerken + Sneltoetsen bewerken Shortcuts - Snelkoppelingen + Sneltoetsen @@ -1600,7 +1602,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. Reset - Standaardwaarden terugzetten + Resetten @@ -1619,7 +1621,7 @@ Als geen enkele modus actief is, doet de Escape-toets niets. clear - duidelijk + wissen diff --git a/YACReader/yacreader_pt.ts b/YACReader/yacreader_pt.ts index dcda1449c..685a47c7e 100644 --- a/YACReader/yacreader_pt.ts +++ b/YACReader/yacreader_pt.ts @@ -24,7 +24,7 @@ Light - Luz + Claro @@ -44,12 +44,12 @@ Remove this user-imported theme - Remova este tema importado pelo usuário + Remover este tema importado pelo usuário Light: - Luz: + Claro: @@ -79,7 +79,7 @@ Open Theme Editor... - Abra o Editor de Tema... + Abrir editor de tema... @@ -89,7 +89,7 @@ The current theme JSON could not be loaded. - O tema atual JSON não pôde ser carregado. + Não foi possível carregar o JSON do tema atual. @@ -145,7 +145,7 @@ - Lastest Page + Latest Page Última Página @@ -166,7 +166,7 @@ - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. Para alterar um atalho, clique duas vezes na combinação de teclas e digite as novas teclas. @@ -181,30 +181,30 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function O atalho "%1" já está atribuído a outra função FileComic - + 7z not found 7z não encontrado - + CRC error on page (%1): some of the pages will not be displayed correctly Erro CRC na página (%1): algumas páginas não serão exibidas corretamente - + Unknown error opening the file Erro desconhecido ao abrir o arquivo - - + + Format not supported Formato não suportado @@ -224,8 +224,8 @@ - Total pages : - Total de páginas : + Total pages: + Total de páginas: @@ -234,16 +234,16 @@ - Page : - Página : + Page: + Página: GoToFlowToolBar - Page : - Página : + Page: + Página: @@ -261,7 +261,7 @@ Changelog - + Registro de alterações @@ -274,7 +274,7 @@ My comics path - Meu caminho de quadrinhos + Caminho dos meus quadrinhos @@ -282,12 +282,12 @@ Tamanho de "Ir para Comic Flow" - + Appearance Aparência - + Options Opções @@ -309,22 +309,22 @@ Clear - Claro + Limpar - + Comics directory - Diretório de quadrinhos + Pasta de quadrinhos - + Restart is needed - Reiniciar é necessário + É necessário reiniciar Display - Mostrar + Exibição @@ -363,28 +363,28 @@ - Scroll behaviour + Scroll behavior Comportamento de rolagem Disable scroll animations and smooth scrolling - Desative animações de rolagem e rolagem suave + Desativar animações de rolagem e rolagem suave Do not turn page using scroll - Não vire a página usando scroll + Não virar a página ao rolar Use single scroll step to turn page - Use uma única etapa de rolagem para virar a página + Usar um único passo de rolagem para virar a página Mouse mode - Modo mouse + Modo do mouse @@ -398,7 +398,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Clique na metade esquerda ou direita da tela para virar as páginas. @@ -437,7 +437,7 @@ If none is active, Escape does nothing. 1. Lupa 2. Dicionário 3. Barra Ir para a página -4. Ecrã inteiro +4. Tela cheia Se nenhum estiver ativo, a tecla Escape não faz nada. @@ -459,7 +459,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Disable mouse over activation - Desativar ativação do mouse sobre + Desativar ativação ao passar o mouse @@ -479,7 +479,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Reset - Reiniciar + Redefinir @@ -494,7 +494,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Enlarge images to fit width/height - Amplie as imagens para caber na largura/altura + Ampliar imagens para caber na largura/altura @@ -529,7 +529,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Bilinear - Interpola??o bilinear + Bilinear @@ -537,17 +537,22 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Lanczos (melhor qualidade) - - General - Em geral + + Bicubic + + General + Geral + + + Page Flow Fluxo de página - + Image adjustment Ajuste de imagem @@ -562,15 +567,15 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. unable to load 7z lib from ./utils - não é possível carregar 7z lib de ./utils + não foi possível carregar a biblioteca 7z de ./utils - + Select custom cover Selecione a capa personalizada - + Images (%1) Imagens (%1) @@ -605,7 +610,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. i - eu + i @@ -620,7 +625,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Segure para piscar o valor selecionado na UI (magenta/alternado/0↔10). Os lançamentos restauram o original. + Segure para destacar o valor selecionado na interface (magenta / alternado / 0↔10). Ao soltar, o original é restaurado. @@ -630,7 +635,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Light - Luz + Claro @@ -640,7 +645,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. ID: - EU IA: + ID: @@ -700,7 +705,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. true - verdadeiro + true @@ -708,7 +713,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. false - falso + false @@ -779,7 +784,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Expected a JSON object. - Esperava um objeto JSON. + Era esperado um objeto JSON. @@ -787,12 +792,12 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - Press 'O' to open comic. + Press 'O' to open a comic. Pressione 'O' para abrir um quadrinho. - Loading...please wait! + Loading... please wait! Carregando... por favor, aguarde! @@ -823,7 +828,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Cover! - Cobrir! + Capa! @@ -846,12 +851,12 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Stripe look - Olhar lista + Aparência em faixas Overlapped Stripe look - Olhar lista sobreposta + Aparência em faixas sobrepostas @@ -886,17 +891,17 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Cover gap - Cubra a lacuna + Espaço entre capas Central gap - Lacuna central + Espaço central Zoom - Amplia??o + Zoom @@ -911,7 +916,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Cover Angle - Ângulo de cobertura + Ângulo da capa @@ -940,8 +945,8 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Use VSync (melhora a qualidade da imagem em modo tela cheia, pior desempenho) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Usar VSync (melhora a qualidade da imagem em modo tela cheia, mas reduz o desempenho) @@ -974,27 +979,27 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Open image folder - Abra a pasta de imagens + Abrir pasta de imagens Open latest comic - Abra o último quadrinho + Abrir o último quadrinho Open the latest comic opened in the previous reading session - Abra o último quadrinho aberto na sessão de leitura anterior + Abrir o último quadrinho aberto na sessão de leitura anterior Clear - Claro + Limpar Clear open recent list - Limpar lista recente aberta + Limpar lista de abertos recentemente @@ -1003,25 +1008,25 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - - - + + + Save current page Salvar página atual - - - - + + + + Extract page(s) - + Extrair página(s) Extract page(s) from the original source - + Extrair página(s) da fonte original @@ -1030,8 +1035,8 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - - + + Open previous comic Abrir quadrinho anterior @@ -1042,8 +1047,8 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - - + + Open next comic Abrir próximo quadrinho @@ -1054,8 +1059,8 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - - + + Go to previous page Ir para a página anterior @@ -1066,8 +1071,8 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - - + + Go to next page Ir para a próxima página @@ -1094,7 +1099,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Show full size - Mostrar tamanho grande + Mostrar tamanho original @@ -1124,12 +1129,12 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Zoom+ - Ampliar + Zoom+ Zoom- - Reduzir + Zoom- @@ -1144,12 +1149,12 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Double page mode - Modo dupla página + Modo de página dupla Switch to double page mode - Alternar para o modo dupla página + Alternar para o modo de página dupla @@ -1168,7 +1173,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. - Go to page ... + Go to page... Ir para a página... @@ -1194,6 +1199,8 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. + + Magnifying glass Lupa @@ -1225,7 +1232,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Show keyboard shortcuts - Mostrar teclas de atalhos + Mostrar atalhos de teclado @@ -1299,222 +1306,217 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Janela - + Open Comic Abrir Quadrinho - + Comic files Arquivos de quadrinhos - + Open folder Abrir pasta - - + + Comics Quadrinhos - + Toggle fullscreen mode Alternar modo de tela cheia - + Hide/show toolbar Ocultar/mostrar barra de ferramentas - - + + General - Em geral + Geral - + Size up magnifying glass - Dimensione a lupa + Aumentar o tamanho da lupa - + Size down magnifying glass Diminuir o tamanho da lupa - + Zoom in magnifying glass - Zoom na lupa + Aumentar o zoom da lupa - + Zoom out magnifying glass Diminuir o zoom da lupa - + Reset magnifying glass Redefinir lupa - - - Magnifiying glass - Lupa - - - + Toggle between fit to width and fit to height Alternar entre ajustar à largura e ajustar à altura - - - Page adjustement + + + Page adjustment + Page adjustement Ajuste de página - + The current page is not ready. Please try again later. A página atual ainda não está pronta. Por favor, tente novamente mais tarde. - + Overwrite file? - + Substituir arquivo? - + The file already exists. Do you want to overwrite it? - + O arquivo já existe. Deseja substituí-lo? - + The current page could not be saved. - Não foi possível guardar a página atual. + Não foi possível salvar a página atual. - + The selected pages are not ready. Please try again later. As páginas selecionadas ainda não estão prontas. Por favor, tente novamente mais tarde. - + The current page could not be extracted. - + Não foi possível extrair a página atual. - + Overwrite files? - + Substituir arquivos? - + Some files already exist. Do you want to overwrite them? - + Alguns arquivos já existem. Deseja substituí-los? - + Some pages could not be extracted. - + Não foi possível extrair algumas páginas. - + Autoscroll down Rolagem automática para baixo - + Autoscroll up Rolagem automática para cima - + Autoscroll forward, horizontal first Rolagem automática para frente, horizontal primeiro - + Autoscroll backward, horizontal first Rolagem automática para trás, horizontal primeiro - + Autoscroll forward, vertical first Rolagem automática para frente, vertical primeiro - + Autoscroll backward, vertical first Rolagem automática para trás, vertical primeiro - + Move down Mover para baixo - + Move up - Subir + Mover para cima - + Move left Mover para a esquerda - + Move right Mover para a direita - + Go to the first page - Vá para a primeira página + Ir para a primeira página - + Go to the last page Ir para a última página - + Offset double page to the left Deslocar página dupla para a esquerda - + Offset double page to the right Deslocar página dupla para a direita - - + + Reading Leitura - + There is a new version available Há uma nova versão disponível - + Do you want to download the new version? Você deseja baixar a nova versão? - + Remind me in 14 days Lembre-me em 14 dias - + Not now Agora não @@ -1524,19 +1526,19 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Release notes are not available. - + As notas de versão não estão disponíveis. Previous versions - + Versões anteriores YACReaderFieldEdit - + Click to overwrite Clique para substituir @@ -1550,9 +1552,9 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. YACReaderFieldPlainTextEdit - - - + + + Click to overwrite Clique para substituir @@ -1582,7 +1584,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Customize the keyboard shortcuts used by the application. - Personalize os atalhos de teclado utilizados pela aplicação. + Personalize os atalhos de teclado utilizados pelo aplicativo. @@ -1600,7 +1602,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. Reset - Reiniciar + Redefinir @@ -1619,7 +1621,7 @@ Se nenhum estiver ativo, a tecla Escape não faz nada. clear - claro + limpar diff --git a/YACReader/yacreader_pt_PT.ts b/YACReader/yacreader_pt_PT.ts new file mode 100644 index 000000000..858e9810b --- /dev/null +++ b/YACReader/yacreader_pt_PT.ts @@ -0,0 +1,1631 @@ + + + + + ActionsShortcutsModel + + + None + Nenhum + + + + AppearanceTabWidget + + + Color scheme + Esquema de cores + + + + System + Sistema + + + + Light + Claro + + + + Dark + Escuro + + + + Custom + Personalizado + + + + Remove + Remover + + + + Remove this user-imported theme + Remover este tema importado pelo utilizador + + + + Light: + Claro: + + + + Dark: + Escuro: + + + + Custom: + Personalizado: + + + + Import theme... + Importar tema... + + + + Theme + Tema + + + + Theme editor + Editor de temas + + + + Open Theme Editor... + Abrir editor de temas... + + + + Theme editor error + Erro do editor de temas + + + + The current theme JSON could not be loaded. + Não foi possível carregar o JSON do tema atual. + + + + Import theme + Importar tema + + + + JSON files (*.json);;All files (*) + Ficheiros JSON (*.json);;Todos os ficheiros (*) + + + + Could not import theme from: +%1 + Não foi possível importar o tema de: +%1 + + + + Could not import theme from: +%1 + +%2 + Não foi possível importar o tema de: +%1 + +%2 + + + + Import failed + Falha na importação + + + + BookmarksDialog + + + Latest Page + Última página + + + + Close + Fechar + + + + Click on any image to go to the bookmark + Clique em qualquer imagem para ir para o marcador + + + + + Loading... + A carregar... + + + + ContinuousPageWidget + + + Loading page %1 + A carregar a página %1 + + + + EditShortcutsDialog + + + Restore defaults + Restaurar predefinições + + + + To change a shortcut, double-click the key combination and type the new keys. + Para alterar um atalho, faça duplo clique na combinação de teclas e introduza as novas teclas. + + + + Shortcuts settings + Definições de atalhos + + + + Shortcut in use + Atalho em utilização + + + + The shortcut "%1" is already assigned to another function + O atalho "%1" já está atribuído a outra função + + + + FileComic + + + CRC error on page (%1): some of the pages will not be displayed correctly + Erro de CRC na página (%1): algumas páginas não serão apresentadas corretamente + + + + Unknown error opening the file + Erro desconhecido ao abrir o ficheiro + + + + 7z not found + 7z não encontrado + + + + + Format not supported + Formato não suportado + + + + GoToDialog + + + Page: + Página: + + + + Go To + Ir para + + + + Cancel + Cancelar + + + + + Total pages: + Total de páginas: + + + + Go to... + Ir para... + + + + GoToFlowToolBar + + + Page: + Página: + + + + HelpAboutDialog + + + About + Acerca de + + + + Help + Ajuda + + + + System info + Informações do sistema + + + + Changelog + Registo de alterações + + + + OptionsDialog + + + "Go to flow" size + Tamanho do "Ir para Comic Flow" + + + + My comics path + Caminho das minhas bandas desenhadas + + + + Background color + Cor de fundo + + + + Choose + Escolher + + + + Quick Navigation Mode + Modo de navegação rápida + + + + Disable mouse over activation + Desativar ativação ao passar o rato + + + + Restart is needed + É necessário reiniciar + + + + Brightness + Brilho + + + + Language + Idioma + + + + Application language + Idioma da aplicação + + + + System default + Predefinição do sistema + + + + Display + Apresentação + + + + Show time in current page information label + Mostrar a hora na etiqueta de informação da página atual + + + + Magnifying glass + Lupa + + + + Circular magnifying glass + Lupa circular + + + + Draw a ring around the circular magnifying glass + Desenhar um anel à volta da lupa circular + + + + Ease cursor movement toward the edges + Suavizar o movimento do cursor em direção às extremidades + + + + Clear + Limpar + + + + Scroll behavior + Comportamento de deslocação + + + + Disable scroll animations and smooth scrolling + Desativar animações de deslocação e deslocação suave + + + + Do not turn page using scroll + Não virar a página com a deslocação + + + + Use single scroll step to turn page + Usar um único passo de deslocação para virar a página + + + + Mouse mode + Modo do rato + + + + Only Back/Forward buttons can turn pages + Só os botões Recuar/Avançar podem virar as páginas + + + + Use the Left/Right buttons to turn pages. + Utilize os botões Esquerda/Direita para virar as páginas. + + + + Click the left or right half of the screen to turn pages. + Clique na metade esquerda ou direita do ecrã para virar as páginas. + + + + Escape key + Tecla Escape + + + + Quit the reader + Sair do leitor + + + + Cancel the active mode + Cancelar o modo ativo + + + + Escape closes the reader, even while a mode is active. + A tecla Escape fecha o leitor, mesmo quando há um modo ativo. + + + + Escape cancels the first of these that is active: + +1. Magnifying glass +2. Dictionary +3. Go to page bar +4. Fullscreen + +If none is active, Escape does nothing. + Tooltip listing the order in which modes are cancelled. Only the first active mode in the list is cancelled per Escape keypress. + A tecla Escape cancela o primeiro destes modos que estiver ativo: + +1. Lupa +2. Dicionário +3. Barra Ir para a página +4. Ecrã inteiro + +Se nenhum estiver ativo, a tecla Escape não faz nada. + + + + Use software-rendered "go to flow" (requires restart) + Usar "Ir para Comic Flow" renderizado por software (requer reinício) + + + + Use a simple horizontal list of page thumbnails instead of the GPU-rendered 3D flow. Enable this if reading is not smooth on your system. + Utilize uma lista horizontal simples de miniaturas das páginas em vez do fluxo 3D renderizado pela GPU. Ative esta opção se a leitura não for fluida no seu sistema. + + + + Contrast + Contraste + + + + Gamma + Gama + + + + Reset + Repor + + + + Image options + Opções de imagem + + + + Fit options + Opções de ajuste + + + + Enlarge images to fit width/height + Ampliar imagens para caberem na largura/altura + + + + Double Page options + Opções de página dupla + + + + Show covers as single page + Mostrar capas como página única + + + + Show book fold shadow + Mostrar sombra na dobra do livro + + + + Scaling + Dimensionamento + + + + Scaling method + Método de dimensionamento + + + + Nearest (fast, low quality) + Vizinho mais próximo (rápido, baixa qualidade) + + + + Bilinear + Bilinear + + + + Lanczos (better quality) + Lanczos (melhor qualidade) + + + + Bicubic + + + + + General + Geral + + + + Page Flow + Fluxo de páginas + + + + Image adjustment + Ajuste de imagem + + + + Appearance + Aspeto + + + + Options + Opções + + + + Comics directory + Pasta das bandas desenhadas + + + + QObject + + + Select custom cover + Selecionar capa personalizada + + + + Images (%1) + Imagens (%1) + + + + 7z lib not found + Biblioteca 7z não encontrada + + + + unable to load 7z lib from ./utils + não foi possível carregar a biblioteca 7z a partir de ./utils + + + + The file could not be read or is not valid JSON. + Não foi possível ler o ficheiro ou este não é um JSON válido. + + + + This theme is for %1, not %2. + Este tema é para %1, não para %2. + + + + ThemeEditorDialog + + + Theme Editor + Editor de temas + + + + + + + + + + + - + - + + + + i + i + + + + Expand all + Expandir tudo + + + + Collapse all + Recolher tudo + + + + Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. + Mantenha premido para fazer piscar o valor selecionado na interface (magenta / alternado / 0↔10). Ao soltar, o original é restaurado. + + + + Search… + Pesquisar… + + + + Light + Claro + + + + Dark + Escuro + + + + ID: + ID: + + + + Display name: + Nome de apresentação: + + + + Variant: + Variante: + + + + Theme info + Informações do tema + + + + Parameter + Parâmetro + + + + Value + Valor + + + + Save and apply + Guardar e aplicar + + + + Export to file... + Exportar para ficheiro... + + + + Load from file... + Carregar a partir de ficheiro... + + + + Close + Fechar + + + + Double-click to edit color + Faça duplo clique para editar a cor + + + + + + + + + true + true + + + + + + + false + false + + + + Double-click to toggle + Faça duplo clique para alternar + + + + Double-click to edit value + Faça duplo clique para editar o valor + + + + + + Edit: %1 + Editar: %1 + + + + Save theme + Guardar tema + + + + + JSON files (*.json);;All files (*) + Ficheiros JSON (*.json);;Todos os ficheiros (*) + + + + Save failed + Falha ao guardar + + + + Could not open file for writing: +%1 + Não foi possível abrir o ficheiro para escrita: +%1 + + + + Load theme + Carregar tema + + + + + + Load failed + Falha ao carregar + + + + Could not open file: +%1 + Não foi possível abrir o ficheiro: +%1 + + + + Invalid JSON: +%1 + JSON inválido: +%1 + + + + Expected a JSON object. + Era esperado um objeto JSON. + + + + Viewer + + + + Press 'O' to open a comic. + Prima 'O' para abrir uma banda desenhada. + + + + Not found + Não encontrado + + + + Comic not found + Banda desenhada não encontrada + + + + Error opening comic + Erro ao abrir a banda desenhada + + + + CRC Error + Erro de CRC + + + + Loading... please wait! + A carregar... aguarde, por favor! + + + + Page not available! + Página indisponível! + + + + Cover! + Capa! + + + + Last page! + Última página! + + + + YACReader3DFlowConfigWidget + + + Presets: + Predefinições: + + + + Classic look + Aspeto clássico + + + + Stripe look + Aspeto em faixas + + + + Overlapped Stripe look + Aspeto em faixas sobrepostas + + + + Modern look + Aspeto moderno + + + + Roulette look + Aspeto de roleta + + + + Show advanced settings + Mostrar definições avançadas + + + + Custom: + Personalizado: + + + + View angle + Ângulo de visão + + + + Position + Posição + + + + Cover gap + Espaço entre capas + + + + Central gap + Espaço central + + + + Zoom + Zoom + + + + Y offset + Deslocamento Y + + + + Z offset + Deslocamento Z + + + + Cover Angle + Ângulo das capas + + + + Visibility + Visibilidade + + + + Light + Luz + + + + Max angle + Ângulo máximo + + + + Low Performance + Baixo desempenho + + + + High Performance + Alto desempenho + + + + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Usar VSync (melhora a qualidade da imagem em modo de ecrã inteiro, mas reduz o desempenho) + + + + Performance: + Desempenho: + + + + YACReader::MainWindowViewer + + + &Open + &Abrir + + + + Open a comic + Abrir uma banda desenhada + + + + New instance + Nova instância + + + + Open Folder + Abrir pasta + + + + Open image folder + Abrir pasta de imagens + + + + Open latest comic + Abrir a banda desenhada mais recente + + + + Open the latest comic opened in the previous reading session + Abrir a banda desenhada mais recente aberta na sessão de leitura anterior + + + + Clear + Limpar + + + + Clear open recent list + Limpar lista de abertos recentemente + + + + Save + Guardar + + + + + + + Save current page + Guardar página atual + + + + + + + + Extract page(s) + Extrair página(s) + + + + Extract page(s) from the original source + Extrair página(s) da origem original + + + + Previous Comic + Banda desenhada anterior + + + + + + Open previous comic + Abrir banda desenhada anterior + + + + Next Comic + Banda desenhada seguinte + + + + + + Open next comic + Abrir banda desenhada seguinte + + + + &Previous + A&nterior + + + + + + Go to previous page + Ir para a página anterior + + + + &Next + &Seguinte + + + + + + Go to next page + Ir para a página seguinte + + + + Fit Height + Ajustar altura + + + + Fit image to height + Ajustar imagem à altura + + + + Fit Width + Ajustar largura + + + + Fit image to width + Ajustar imagem à largura + + + + Show full size + Mostrar tamanho real + + + + Fit to page + Ajustar à página + + + + Continuous scroll + Deslocação contínua + + + + Switch to continuous scroll mode + Mudar para o modo de deslocação contínua + + + + Reset zoom + Repor Zoom + + + + Show zoom slider + Mostrar controlo de deslize do Zoom + + + + Zoom+ + Zoom+ + + + + Zoom- + Zoom- + + + + Rotate image to the left + Rodar imagem para a esquerda + + + + Rotate image to the right + Rodar imagem para a direita + + + + Double page mode + Modo de página dupla + + + + Switch to double page mode + Mudar para o modo de página dupla + + + + Double page manga mode + Modo manga de página dupla + + + + Reverse reading order in double page mode + Inverter a ordem de leitura no modo de página dupla + + + + Go To + Ir para + + + + Go to page... + Ir para a página... + + + + Options + Opções + + + + YACReader options + Opções do YACReader + + + + + Help + Ajuda + + + + Help, About YACReader + Ajuda, Acerca do YACReader + + + + + + Magnifying glass + Lupa + + + + Switch Magnifying glass + Alternar lupa + + + + Set bookmark + Definir marcador + + + + Set a bookmark on the current page + Definir um marcador na página atual + + + + Show bookmarks + Mostrar marcadores + + + + Show the bookmarks of the current comic + Mostrar os marcadores da banda desenhada atual + + + + Show keyboard shortcuts + Mostrar atalhos de teclado + + + + Show Info + Mostrar informações + + + + Escape + Escape + + + + Escape key: quit, or cancel the active mode + Tecla Escape: sair ou cancelar o modo ativo + + + + Close + Fechar + + + + Show Dictionary + Mostrar dicionário + + + + Show go to flow + Mostrar "Ir para Comic Flow" + + + + Edit shortcuts + Editar atalhos + + + + &File + &Ficheiro + + + + + Open recent + Abrir recentes + + + + File + Ficheiro + + + + Edit + Editar + + + + View + Ver + + + + Go + Ir + + + + Window + Janela + + + + Open Comic + Abrir banda desenhada + + + + Comic files + Ficheiros de banda desenhada + + + + Open folder + Abrir pasta + + + + The current page is not ready. Please try again later. + A página atual ainda não está pronta. Tente novamente mais tarde. + + + + Overwrite file? + Substituir o ficheiro? + + + + The file already exists. Do you want to overwrite it? + O ficheiro já existe. Pretende substituí-lo? + + + + The current page could not be saved. + Não foi possível guardar a página atual. + + + + The selected pages are not ready. Please try again later. + As páginas selecionadas ainda não estão prontas. Tente novamente mais tarde. + + + + The current page could not be extracted. + Não foi possível extrair a página atual. + + + + Overwrite files? + Substituir os ficheiros? + + + + Some files already exist. Do you want to overwrite them? + Alguns ficheiros já existem. Pretende substituí-los? + + + + Some pages could not be extracted. + Não foi possível extrair algumas páginas. + + + + + Comics + Bandas desenhadas + + + + + General + Geral + + + + + Page adjustment + Ajuste da página + + + + + Reading + Leitura + + + + Toggle fullscreen mode + Alternar modo de ecrã inteiro + + + + Hide/show toolbar + Ocultar/mostrar barra de ferramentas + + + + Size up magnifying glass + Aumentar o tamanho da lupa + + + + Size down magnifying glass + Diminuir o tamanho da lupa + + + + Zoom in magnifying glass + Aumentar o Zoom da lupa + + + + Zoom out magnifying glass + Diminuir o Zoom da lupa + + + + Reset magnifying glass + Repor lupa + + + + Toggle between fit to width and fit to height + Alternar entre ajustar à largura e ajustar à altura + + + + Autoscroll down + Deslocação automática para baixo + + + + Autoscroll up + Deslocação automática para cima + + + + Autoscroll forward, horizontal first + Deslocação automática para a frente, primeiro na horizontal + + + + Autoscroll backward, horizontal first + Deslocação automática para trás, primeiro na horizontal + + + + Autoscroll forward, vertical first + Deslocação automática para a frente, primeiro na vertical + + + + Autoscroll backward, vertical first + Deslocação automática para trás, primeiro na vertical + + + + Move down + Mover para baixo + + + + Move up + Mover para cima + + + + Move left + Mover para a esquerda + + + + Move right + Mover para a direita + + + + Go to the first page + Ir para a primeira página + + + + Go to the last page + Ir para a última página + + + + Offset double page to the left + Deslocar a página dupla para a esquerda + + + + Offset double page to the right + Deslocar a página dupla para a direita + + + + There is a new version available + Existe uma nova versão disponível + + + + Do you want to download the new version? + Pretende transferir a nova versão? + + + + Remind me in 14 days + Lembrar-me daqui a 14 dias + + + + Not now + Agora não + + + + YACReader::WhatsNewDialog + + + Release notes are not available. + As notas de versão não estão disponíveis. + + + + Previous versions + Versões anteriores + + + + YACReaderFieldEdit + + + + Click to overwrite + Clique para substituir + + + + Restore to default + Restaurar predefinição + + + + YACReaderFieldPlainTextEdit + + + + + + Click to overwrite + Clique para substituir + + + + Restore to default + Restaurar predefinição + + + + YACReaderOptionsDialog + + + Save + Guardar + + + + Cancel + Cancelar + + + + Keyboard shortcuts + Atalhos de teclado + + + + Customize the keyboard shortcuts used by the application. + Personalize os atalhos de teclado utilizados pela aplicação. + + + + Edit shortcuts + Editar atalhos + + + + Shortcuts + Atalhos + + + + YACReaderSlider + + + Reset + Repor + + + + YACReaderTranslator + + + YACReader translator + Tradutor do YACReader + + + + + Translation + Tradução + + + + clear + limpar + + + + Service not available + Serviço indisponível + + + diff --git a/YACReader/yacreader_ru.ts b/YACReader/yacreader_ru.ts index e523571de..04d534a42 100644 --- a/YACReader/yacreader_ru.ts +++ b/YACReader/yacreader_ru.ts @@ -6,7 +6,7 @@ None - Никто + Нет @@ -24,22 +24,22 @@ Light - Осветить + Светлая Dark - Темный + Темная Custom - Обычай + Пользовательская Remove - Удалять + Удалить @@ -49,17 +49,17 @@ Light: - Свет: + Светлая: Dark: - Темный: + Темная: Custom: - Пользовательский: + Пользовательская: @@ -141,11 +141,11 @@ Click on any image to go to the bookmark - Нажмите на любое изображение чтобы перейти к закладке + Нажмите на любое изображение, чтобы перейти к закладке - Lastest Page + Latest Page Последняя страница @@ -176,35 +176,35 @@ - The shortcut "%1" is already assigned to other function - Сочетание клавиш "%1" уже назначено для другой функции + The shortcut "%1" is already assigned to another function + Сочетание клавиш «%1» уже назначено для другой функции - To change a shortcut, double click in the key combination and type the new keys. - Чтобы изменить горячую клавишу дважды щелкните по выбранной комбинации клавиш и введите новые сочетания клавиш. + To change a shortcut, double-click the key combination and type the new keys. + Чтобы изменить горячую клавишу, дважды щелкните по выбранной комбинации клавиш и введите новые сочетания клавиш. FileComic - - + + Format not supported Формат не поддерживается - + 7z not found 7z не найден - + Unknown error opening the file Неизвестная ошибка при открытии файла - + CRC error on page (%1): some of the pages will not be displayed correctly Ошибка контрольной суммы CRC на странице (%1): некоторые страницы будут отображаться неправильно @@ -224,8 +224,8 @@ - Total pages : - Общее количество страниц : + Total pages: + Общее количество страниц: @@ -234,16 +234,16 @@ - Page : - Страница : + Page: + Страница: GoToFlowToolBar - Page : - Страница : + Page: + Страница: @@ -261,7 +261,7 @@ Changelog - + Журнал изменений @@ -279,22 +279,22 @@ Reset - Вернуть к первоначальным значениям + Сбросить My comics path - Папка комиксов + Путь к моим комиксам - + Image adjustment Настройка изображения "Go to flow" size - Размер "Перейти к Comic Flow" + Размер «Перейти к Comic Flow» @@ -312,12 +312,12 @@ Контраст - + Appearance - Появление + Внешний вид - + Options Настройки @@ -342,19 +342,19 @@ Очистить - + Comics directory Папка комиксов Quick Navigation Mode - Ползунок для быстрой навигации по страницам + Режим быстрой навигации Display - Отображать + Отображение @@ -388,7 +388,7 @@ - Scroll behaviour + Scroll behavior Поведение прокрутки @@ -399,12 +399,12 @@ Do not turn page using scroll - Не переворачивайте страницу с помощью прокрутки + Не переворачивать страницу с помощью прокрутки Use single scroll step to turn page - Используйте один шаг прокрутки, чтобы перевернуть страницу + Использовать один шаг прокрутки, чтобы перевернуть страницу @@ -414,7 +414,7 @@ Only Back/Forward buttons can turn pages - Только кнопки «Назад/Вперед» могут перелистывать страницы. + Только кнопки «Назад/Вперед» могут перелистывать страницы @@ -423,7 +423,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Нажмите левую или правую половину экрана, чтобы перелистывать страницы. @@ -457,9 +457,9 @@ If none is active, Escape does nothing. Tooltip listing the order in which modes are cancelled. Only the first active mode in the list is cancelled per Escape keypress. - Клавиша Esc отключает первый активный режим из списка: + Клавиша Esc отменяет первый активный режим из списка: -1. Лупа +1. Увеличительное стекло 2. Словарь 3. Панель перехода к странице 4. Полноэкранный режим @@ -469,7 +469,7 @@ If none is active, Escape does nothing. Use software-rendered "go to flow" (requires restart) - Использовать программную отрисовку "Перейти к Comic Flow" (требуется перезапуск) + Использовать программную отрисовку «Перейти к Comic Flow» (требуется перезапуск) @@ -504,20 +504,25 @@ If none is active, Escape does nothing. Bilinear - Билинейный + Bilinear Lanczos (better quality) - Ланцос (лучшее качество) + Lanczos (лучшее качество) - + + Bicubic + + + + Page Flow - Поток Страниц + Поток страниц - + General Общие @@ -527,19 +532,19 @@ If none is active, Escape does nothing. Яркость - + Restart is needed - Требуется перезагрузка + Требуется перезапуск Fit options - Варианты подгонки + Параметры подгонки Enlarge images to fit width/height - Увеличьте изображения по ширине/высоте + Увеличивать изображения по ширине/высоте @@ -562,15 +567,15 @@ If none is active, Escape does nothing. unable to load 7z lib from ./utils - не удается загрузить 7z lib из ./ utils + не удается загрузить библиотеку 7z из ./utils - + Select custom cover - Выбрать индивидуальную обложку + Выбрать пользовательскую обложку - + Images (%1) Изображения (%1) @@ -605,7 +610,7 @@ If none is active, Escape does nothing. i - я + i @@ -620,7 +625,7 @@ If none is active, Escape does nothing. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Удерживайте, чтобы выбранное значение мигало в пользовательском интерфейсе (пурпурный / переключено / 0↔10). Релизы восстанавливают оригинал. + Удерживайте, чтобы выбранное значение мигало в интерфейсе (пурпурный / переключено / 0↔10). При отпускании восстанавливается исходное значение. @@ -630,17 +635,17 @@ If none is active, Escape does nothing. Light - Осветить + Светлая Dark - Темный + Темная ID: - ИДЕНТИФИКАТОР: + ID: @@ -665,12 +670,12 @@ If none is active, Escape does nothing. Value - Ценить + Значение Save and apply - Сохраните и примените + Сохранить и применить @@ -700,7 +705,7 @@ If none is active, Escape does nothing. true - истинный + true @@ -708,7 +713,7 @@ If none is active, Escape does nothing. false - ЛОЖЬ + false @@ -741,7 +746,7 @@ If none is active, Escape does nothing. Save failed - Сохранить не удалось + Сохранение не удалось @@ -792,8 +797,8 @@ If none is active, Escape does nothing. - Press 'O' to open comic. - Нажмите "O" чтобы открыть комикс. + Press 'O' to open a comic. + Нажмите «O», чтобы открыть комикс. @@ -803,7 +808,7 @@ If none is active, Escape does nothing. Cover! - Начало! + Обложка! @@ -823,12 +828,12 @@ If none is active, Escape does nothing. Last page! - Конец! + Последняя страница! - Loading...please wait! - Загрузка... Пожалуйста подождите! + Loading... please wait! + Загрузка... Пожалуйста, подождите! @@ -871,7 +876,7 @@ If none is active, Escape does nothing. Custom: - Пользовательский: + Пользовательская: @@ -886,17 +891,17 @@ If none is active, Escape does nothing. Cover gap - Осветить разрыв + Зазор между обложками Central gap - Сфокусировать разрыв + Центральный зазор Zoom - Масштабировать + Zoom @@ -911,17 +916,17 @@ If none is active, Escape does nothing. Cover Angle - Охватить угол + Угол обложек Visibility - Прозрачность + Видимость Light - Осветить + Освещение @@ -940,8 +945,8 @@ If none is active, Escape does nothing. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Использовать VSync (повысить формат изображения в полноэкранном режиме , хуже производительность) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Использовать VSync (улучшает качество изображения в полноэкранном режиме, снижает производительность) @@ -984,7 +989,7 @@ If none is active, Escape does nothing. Open the latest comic opened in the previous reading session - Открыть комикс открытый в предыдущем сеансе чтения + Открыть последний комикс, открытый в предыдущем сеансе чтения @@ -1003,25 +1008,25 @@ If none is active, Escape does nothing. - - - + + + Save current page - Сохранить текущию страницу + Сохранить текущую страницу - - - - + + + + Extract page(s) - + Извлечь страницу(ы) Extract page(s) from the original source - + Извлечь страницу(ы) из исходного файла @@ -1030,10 +1035,10 @@ If none is active, Escape does nothing. - - + + Open previous comic - Открыть предыдуший комикс + Открыть предыдущий комикс @@ -1042,8 +1047,8 @@ If none is active, Escape does nothing. - - + + Open next comic Открыть следующий комикс @@ -1054,8 +1059,8 @@ If none is active, Escape does nothing. - - + + Go to previous page Перейти к предыдущей странице @@ -1066,8 +1071,8 @@ If none is active, Escape does nothing. - - + + Go to next page Перейти к следующей странице @@ -1079,7 +1084,7 @@ If none is active, Escape does nothing. Fit image to height - Подогнать по высоте + Подогнать изображение по высоте @@ -1089,7 +1094,7 @@ If none is active, Escape does nothing. Fit image to width - Подогнать по ширине + Подогнать изображение по ширине @@ -1114,22 +1119,22 @@ If none is active, Escape does nothing. Reset zoom - Сбросить масштаб + Сбросить Zoom Show zoom slider - Показать ползунок масштабирования + Показать ползунок Zoom Zoom+ - Увеличить масштаб + Zoom+ Zoom- - Уменьшить масштаб + Zoom- @@ -1149,7 +1154,7 @@ If none is active, Escape does nothing. Switch to double page mode - Двухстраничный режим + Переключиться в двухстраничный режим @@ -1159,7 +1164,7 @@ If none is active, Escape does nothing. Reverse reading order in double page mode - Двухстраничный режим манги + Обратный порядок чтения в двухстраничном режиме @@ -1168,7 +1173,7 @@ If none is active, Escape does nothing. - Go to page ... + Go to page... Перейти к странице... @@ -1179,7 +1184,7 @@ If none is active, Escape does nothing. YACReader options - Настройки + Настройки YACReader @@ -1190,17 +1195,19 @@ If none is active, Escape does nothing. Help, About YACReader - Справка + Справка, о программе YACReader + + Magnifying glass Увеличительное стекло Switch Magnifying glass - Увеличительное стекло + Включить/выключить увеличительное стекло @@ -1250,12 +1257,12 @@ If none is active, Escape does nothing. Show Dictionary - Переводчик YACreader + Показать словарь Show go to flow - Показать "Перейти к Comic Flow" + Показать «Перейти к Comic Flow» @@ -1265,7 +1272,7 @@ If none is active, Escape does nothing. &File - &Отображать панель инструментов + &Файл @@ -1286,7 +1293,7 @@ If none is active, Escape does nothing. View - Посмотреть + Вид @@ -1299,222 +1306,217 @@ If none is active, Escape does nothing. Окно - + Open Comic Открыть комикс - + Comic files - Файлы комикса + Файлы комиксов - + Open folder Открыть папку - - + + Comics - Комикс + Комиксы - + Toggle fullscreen mode Полноэкранный режим включить/выключить - + Hide/show toolbar Показать/скрыть панель инструментов - - + + General Общие - + Size up magnifying glass Увеличение размера окошка увеличительного стекла - + Size down magnifying glass Уменьшение размера окошка увеличительного стекла - + Zoom in magnifying glass - Увеличить + Увеличить Zoom увеличительного стекла - + Zoom out magnifying glass - Уменьшить + Уменьшить Zoom лупы - + Reset magnifying glass Сбросить увеличительное стекло - - - Magnifiying glass - Увеличительное стекло - - - + Toggle between fit to width and fit to height Переключение режима подгонки страницы по ширине/высоте - - - Page adjustement + + + Page adjustment + Page adjustement Настройка страницы - + The current page is not ready. Please try again later. Текущая страница ещё не готова. Повторите попытку позже. - + Overwrite file? - + Перезаписать файл? - + The file already exists. Do you want to overwrite it? - + Файл уже существует. Перезаписать его? - + The current page could not be saved. Не удалось сохранить текущую страницу. - + The selected pages are not ready. Please try again later. Выбранные страницы ещё не готовы. Повторите попытку позже. - + The current page could not be extracted. - + Не удалось извлечь текущую страницу. - + Overwrite files? - + Перезаписать файлы? - + Some files already exist. Do you want to overwrite them? - + Некоторые файлы уже существуют. Перезаписать их? - + Some pages could not be extracted. - + Не удалось извлечь некоторые страницы. - + Autoscroll down Автопрокрутка вниз - + Autoscroll up Автопрокрутка вверх - + Autoscroll forward, horizontal first - Автопрокрутка вперед, горизонтальная + Автопрокрутка вперед, сначала по горизонтали - + Autoscroll backward, horizontal first - Автопрокрутка назад, горизонтальная + Автопрокрутка назад, сначала по горизонтали - + Autoscroll forward, vertical first - Автопрокрутка вперед, вертикальная + Автопрокрутка вперед, сначала по вертикали - + Autoscroll backward, vertical first - Автопрокрутка назад, вертикальная + Автопрокрутка назад, сначала по вертикали - + Move down Переместить вниз - + Move up Переместить вверх - + Move left Переместить влево - + Move right Переместить вправо - + Go to the first page Перейти к первой странице - + Go to the last page Перейти к последней странице - + Offset double page to the left Смещение разворота влево - + Offset double page to the right Смещение разворота вправо - - + + Reading Чтение - + There is a new version available Доступна новая версия - + Do you want to download the new version? - Хотите загрузить новую версию ? + Хотите загрузить новую версию? - + Remind me in 14 days Напомнить через 14 дней - + Not now Не сейчас @@ -1524,12 +1526,12 @@ If none is active, Escape does nothing. Release notes are not available. - + Примечания к выпуску недоступны. Previous versions - + Предыдущие версии @@ -1541,9 +1543,9 @@ If none is active, Escape does nothing. - + Click to overwrite - Изменить + Щелкните, чтобы перезаписать @@ -1551,15 +1553,15 @@ If none is active, Escape does nothing. Restore to default - Вернуть начальные установки + Восстановить значения по умолчанию - - - + + + Click to overwrite - Изменить + Щелкните, чтобы перезаписать @@ -1600,7 +1602,7 @@ If none is active, Escape does nothing. Reset - Сброс мастштаба + Сбросить diff --git a/YACReader/yacreader_source.ts b/YACReader/yacreader_source.ts index bc2d6dc94..422181e5c 100644 --- a/YACReader/yacreader_source.ts +++ b/YACReader/yacreader_source.ts @@ -125,7 +125,7 @@ BookmarksDialog - Lastest Page + Latest Page @@ -162,7 +162,7 @@ - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. @@ -177,30 +177,30 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - + Unknown error opening the file - + 7z not found - - + + Format not supported @@ -209,7 +209,7 @@ GoToDialog - Page : + Page: @@ -225,7 +225,7 @@ - Total pages : + Total pages: @@ -238,7 +238,7 @@ GoToFlowToolBar - Page : + Page: @@ -298,7 +298,7 @@ - + Restart is needed @@ -359,7 +359,7 @@ - Scroll behaviour + Scroll behavior @@ -394,7 +394,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. @@ -511,32 +511,37 @@ If none is active, Escape does nothing. - - General + + Bicubic - Page Flow + General - Image adjustment + Page Flow + Image adjustment + + + + Appearance - + Options - + Comics directory @@ -544,12 +549,12 @@ If none is active, Escape does nothing. QObject - + Select custom cover - + Images (%1) @@ -773,7 +778,7 @@ If none is active, Escape does nothing. - Press 'O' to open comic. + Press 'O' to open a comic. @@ -798,7 +803,7 @@ If none is active, Escape does nothing. - Loading...please wait! + Loading... please wait! @@ -926,7 +931,7 @@ If none is active, Escape does nothing. - Use VSync (improve the image quality in fullscreen mode, worse performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) @@ -989,18 +994,18 @@ If none is active, Escape does nothing. - - - + + + Save current page - - - - + + + + Extract page(s) @@ -1016,8 +1021,8 @@ If none is active, Escape does nothing. - - + + Open previous comic @@ -1028,8 +1033,8 @@ If none is active, Escape does nothing. - - + + Open next comic @@ -1040,8 +1045,8 @@ If none is active, Escape does nothing. - - + + Go to previous page @@ -1052,8 +1057,8 @@ If none is active, Escape does nothing. - - + + Go to next page @@ -1154,7 +1159,7 @@ If none is active, Escape does nothing. - Go to page ... + Go to page... @@ -1180,6 +1185,8 @@ If none is active, Escape does nothing. + + Magnifying glass @@ -1285,222 +1292,217 @@ If none is active, Escape does nothing. - + Open Comic - + Comic files - + Open folder - + The current page is not ready. Please try again later. - + Overwrite file? - + The file already exists. Do you want to overwrite it? - + The current page could not be saved. - + The selected pages are not ready. Please try again later. - + The current page could not be extracted. - + Overwrite files? - + Some files already exist. Do you want to overwrite them? - + Some pages could not be extracted. - - + + Comics - - + + General - - - Magnifiying glass - - - - - - Page adjustement + + + Page adjustment + Page adjustement - - + + Reading - + Toggle fullscreen mode - + Hide/show toolbar - + Size up magnifying glass - + Size down magnifying glass - + Zoom in magnifying glass - + Zoom out magnifying glass - + Reset magnifying glass - + Toggle between fit to width and fit to height - + Autoscroll down - + Autoscroll up - + Autoscroll forward, horizontal first - + Autoscroll backward, horizontal first - + Autoscroll forward, vertical first - + Autoscroll backward, vertical first - + Move down - + Move up - + Move left - + Move right - + Go to the first page - + Go to the last page - + Offset double page to the left - + Offset double page to the right - + There is a new version available - + Do you want to download the new version? - + Remind me in 14 days - + Not now @@ -1522,7 +1524,7 @@ If none is active, Escape does nothing. YACReaderFieldEdit - + Click to overwrite @@ -1536,9 +1538,9 @@ If none is active, Escape does nothing. YACReaderFieldPlainTextEdit - - - + + + Click to overwrite diff --git a/YACReader/yacreader_tr.ts b/YACReader/yacreader_tr.ts index a4c44daf0..180c45ff1 100644 --- a/YACReader/yacreader_tr.ts +++ b/YACReader/yacreader_tr.ts @@ -24,22 +24,22 @@ Light - Işık + Açık Dark - Karanlık + Koyu Custom - Gelenek + Özel Remove - Kaldırmak + Kaldır @@ -49,17 +49,17 @@ Light: - Işık: + Açık: Dark: - Karanlık: + Koyu: Custom: - Kişisel: + Özel: @@ -89,7 +89,7 @@ The current theme JSON could not be loaded. - Geçerli tema JSON yüklenemedi. + Geçerli temanın JSON verisi yüklenemedi. @@ -141,11 +141,11 @@ Click on any image to go to the bookmark - Yer imine git + Yer imine gitmek için herhangi bir resme tıklayın - Lastest Page + Latest Page Son Sayfa @@ -162,17 +162,17 @@ Restore defaults - Varsayılarları geri yükle + Varsayılanları geri yükle - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. Bir kısayolu değiştirmek için tuş kombinasyonuna çift tıklayın ve yeni tuşları girin. Shortcuts settings - Kısayol oyarları + Kısayol ayarları @@ -181,30 +181,30 @@ - The shortcut "%1" is already assigned to other function - "%1" kısayolu bir başka işleve zaten atanmış + The shortcut "%1" is already assigned to another function + "%1" kısayolu zaten başka bir işleve atanmış FileComic - + 7z not found 7z bulunamadı - + CRC error on page (%1): some of the pages will not be displayed correctly - (%1). sayfada CRC hatası : bazı sayfalar düzgün görüntülenmeyecek + (%1). sayfada CRC hatası: bazı sayfalar düzgün görüntülenmeyecek - + Unknown error opening the file Dosya açılırken bilinmeyen hata - - + + Format not supported Biçim desteklenmiyor @@ -224,7 +224,7 @@ - Total pages : + Total pages: Toplam sayfa: @@ -234,16 +234,16 @@ - Page : - Sayfa : + Page: + Sayfa: GoToFlowToolBar - Page : - Sayfa : + Page: + Sayfa: @@ -261,7 +261,7 @@ Changelog - + Değişiklik günlüğü @@ -279,12 +279,12 @@ Reset - Yeniden başlat + Sıfırla My comics path - Çizgi Romanlarım + Çizgi romanlarımın yolu @@ -309,7 +309,7 @@ Bilinear - Çift doğrusal + Bilinear @@ -317,7 +317,12 @@ Lanczos (daha kaliteli) - + + Bicubic + + + + Image adjustment Resim ayarları @@ -334,7 +339,7 @@ Image options - Sayfa ayarları + Resim seçenekleri @@ -342,12 +347,12 @@ Kontrast - + Appearance Dış görünüş - + Options Ayarlar @@ -372,7 +377,7 @@ Temizle - + Comics directory Çizgi roman konumu @@ -382,12 +387,12 @@ Arka plan rengi - + Page Flow Sayfa akışı - + General Genel @@ -397,19 +402,19 @@ Parlaklık - + Restart is needed Yeniden başlatılmalı Quick Navigation Mode - Hızlı Gezinti Kipi + Hızlı Gezinti Modu Display - Görüntülemek + Görüntü @@ -438,23 +443,23 @@ - Scroll behaviour + Scroll behavior Kaydırma davranışı Disable scroll animations and smooth scrolling - Kaydırma animasyonlarını ve düzgün kaydırmayı devre dışı bırakın + Kaydırma animasyonlarını ve düzgün kaydırmayı devre dışı bırak Do not turn page using scroll - Kaydırmayı kullanarak sayfayı çevirmeyin + Kaydırmayı kullanarak sayfayı çevirme Use single scroll step to turn page - Sayfayı çevirmek için tek kaydırma adımını kullanın + Sayfayı çevirmek için tek kaydırma adımını kullan @@ -473,7 +478,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. Sayfaları çevirmek için ekranın sol veya sağ yarısına tıklayın. @@ -529,7 +534,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Disable mouse over activation - Etkinleştirme üzerinde fareyi devre dışı bırak + Fare üzerine gelince etkinleştirmeyi devre dışı bırak @@ -539,7 +544,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Enlarge images to fit width/height - Genişliğe/yüksekliği sığmaları için resimleri genişlet + Resimleri genişliğe/yüksekliğe sığacak şekilde büyüt @@ -557,20 +562,20 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. 7z lib not found - 7z lib bulunamadı + 7z kütüphanesi bulunamadı unable to load 7z lib from ./utils - ./utils içinden 7z lib yüklenemedi + ./utils içinden 7z kütüphanesi yüklenemedi - + Select custom cover Özel kapak seçin - + Images (%1) Resimler (%1) @@ -605,7 +610,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. i - Ben + i @@ -620,32 +625,32 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Kullanıcı arayüzünde seçilen değeri (macenta / geçişli / 0↔10) yanıp sönmek için basılı tutun. Sürümler orijinali geri yükler. + Kullanıcı arayüzünde seçilen değeri (macenta / geçişli / 0↔10) yanıp söndürmek için basılı tutun. Bırakıldığında orijinal değer geri yüklenir. Search… - Aramak… + Ara… Light - Işık + Açık Dark - Karanlık + Koyu ID: - İD: + ID: Display name: - Ekran adı: + Görünen ad: @@ -700,7 +705,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. true - doğru + true @@ -708,7 +713,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. false - YANLIŞ + false @@ -787,8 +792,8 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - Press 'O' to open comic. - 'O'ya basarak aç. + Press 'O' to open a comic. + Çizgi romanı açmak için 'O' tuşuna basın. @@ -812,7 +817,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - Loading...please wait! + Loading... please wait! Yükleniyor... lütfen bekleyin! @@ -836,7 +841,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Presets: - Hazırlayan: + Ön ayarlar: @@ -866,12 +871,12 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Show advanced settings - Daha fazla ayar göster + Gelişmiş ayarları göster Custom: - Kişisel: + Özel: @@ -891,22 +896,22 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Central gap - Boş merkaz + Merkez boşluğu Zoom - Yakınlaş + Zoom Y offset - Y dengesi + Y ofseti Z offset - Z dengesi + Z ofseti @@ -916,7 +921,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Visibility - Görünülebilirlik + Görünürlük @@ -940,8 +945,8 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - Use VSync (improve the image quality in fullscreen mode, worse performance) - VSync kullan + Use VSync (improves image quality in fullscreen mode, but reduces performance) + VSync kullan (tam ekran modunda görüntü kalitesini artırır, performansı düşürür) @@ -969,12 +974,12 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Open Folder - Dosyayı Aç + Klasörü Aç Open image folder - Resim dosyasınıaç + Resim klasörünü aç @@ -1003,47 +1008,47 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - - - + + + Save current page Geçerli sayfayı kaydet - - - - + + + + Extract page(s) - + Sayfa(ları) çıkar Extract page(s) from the original source - + Sayfa(ları) özgün kaynaktan çıkar Previous Comic - Önce ki çizgi roman + Önceki çizgi roman - - + + Open previous comic Önceki çizgi romanı aç Next Comic - Sırada ki çizgi roman + Sıradaki çizgi roman - - + + Open next comic Sıradaki çizgi romanı aç @@ -1054,8 +1059,8 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - - + + Go to previous page Önceki sayfaya dön @@ -1066,10 +1071,10 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - - + + Go to next page - Sonra ki sayfaya geç + Sonraki sayfaya geç @@ -1079,22 +1084,22 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Fit image to height - Uygun yüksekliğe getir + Resmi yüksekliğe sığdır Fit Width - Uygun Genişlik + Genişliğe Sığdır Fit image to width - Görüntüyü sığdır + Resmi genişliğe sığdır Show full size - Tam erken + Gerçek boyutu göster @@ -1114,32 +1119,32 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Reset zoom - Yakınlaştırmayı sıfırla + Zoom'u sıfırla Show zoom slider - Yakınlaştırma çubuğunu göster + Zoom çubuğunu göster Zoom+ - Yakınlaştır + Zoom+ Zoom- - Uzaklaştır + Zoom- Rotate image to the left - Sayfayı sola yatır + Resmi sola döndür Rotate image to the right - Sayfayı sağa yator + Resmi sağa döndür @@ -1154,12 +1159,12 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Double page manga mode - Çift sayfa manga kipi + Çift sayfa manga modu Reverse reading order in double page mode - Çift sayfa kipinde ters okuma sırası + Çift sayfa modunda ters okuma sırası @@ -1168,8 +1173,8 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - Go to page ... - Sayfata git... + Go to page... + Sayfaya git... @@ -1190,17 +1195,19 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Help, About YACReader - YACReader hakkında yardım ve bilgi + Yardım, YACReader Hakkında + + Magnifying glass Büyüteç Switch Magnifying glass - Büyüteç + Büyüteci aç/kapat @@ -1299,222 +1306,217 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Pencere - + Open Comic Çizgi Romanı Aç - + Comic files Çizgi Roman Dosyaları - + Open folder - Dosyayı aç + Klasörü aç - - + + Comics - Çizgi Roman + Çizgi Romanlar - + Toggle fullscreen mode - Tam ekran kipini aç/kapat + Tam ekran modunu aç/kapat - + Hide/show toolbar Araç çubuğunu göster/gizle - - + + General Genel - + Size up magnifying glass Büyüteci büyüt - + Size down magnifying glass Büyüteci küçült - + Zoom in magnifying glass - Büyüteci yakınlaştır + Büyüteç zoom'unu artır - + Zoom out magnifying glass - Büyüteci uzaklaştır + Büyüteç zoom'unu azalt - + Reset magnifying glass Büyüteci sıfırla - - - Magnifiying glass - Büyüteç - - - + Toggle between fit to width and fit to height Genişliğe sığdır ile yüksekliğe sığdır arasında geçiş yap - - - Page adjustement + + + Page adjustment + Page adjustement Sayfa ayarı - + The current page is not ready. Please try again later. Geçerli sayfa henüz hazır değil. Lütfen daha sonra tekrar deneyin. - + Overwrite file? - + Dosyanın üzerine yazılsın mı? - + The file already exists. Do you want to overwrite it? - + Dosya zaten var. Üzerine yazmak istiyor musunuz? - + The current page could not be saved. Geçerli sayfa kaydedilemedi. - + The selected pages are not ready. Please try again later. Seçilen sayfalar henüz hazır değil. Lütfen daha sonra tekrar deneyin. - + The current page could not be extracted. - + Geçerli sayfa çıkarılamadı. - + Overwrite files? - + Dosyaların üzerine yazılsın mı? - + Some files already exist. Do you want to overwrite them? - + Bazı dosyalar zaten var. Üzerlerine yazmak istiyor musunuz? - + Some pages could not be extracted. - + Bazı sayfalar çıkarılamadı. - + Autoscroll down Otomatik aşağı kaydır - + Autoscroll up Otomatik yukarı kaydır - + Autoscroll forward, horizontal first Otomatik ileri kaydır, önce yatay - + Autoscroll backward, horizontal first Otomatik geri kaydır, önce yatay - + Autoscroll forward, vertical first Otomatik ileri kaydır, önce dikey - + Autoscroll backward, vertical first Otomatik geri kaydır, önce dikey - + Move down Aşağı git - + Move up Yukarı git - + Move left Sola git - + Move right Sağa git - + Go to the first page İlk sayfaya git - + Go to the last page En son sayfaya git - + Offset double page to the left Çift sayfayı sola kaydır - + Offset double page to the right Çift sayfayı sağa kaydır - - + + Reading Okuma - + There is a new version available - Yeni versiyon mevcut + Yeni sürüm mevcut - + Do you want to download the new version? - Yeni versiyonu indirmek ister misin ? + Yeni sürümü indirmek ister misiniz? - + Remind me in 14 days 14 gün içinde hatırlat - + Not now Şimdi değil @@ -1524,12 +1526,12 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Release notes are not available. - + Sürüm notları kullanılamıyor. Previous versions - + Önceki sürümler @@ -1541,7 +1543,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - + Click to overwrite Üzerine yazmak için tıkla @@ -1555,9 +1557,9 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. - - - + + + Click to overwrite Üstüne yazmak için tıkla @@ -1600,7 +1602,7 @@ Hiçbiri etkin değilse Escape tuşu hiçbir şey yapmaz. Reset - Yeniden başlat + Sıfırla diff --git a/YACReader/yacreader_zh_CN.ts b/YACReader/yacreader_zh_CN.ts index 253f2c22f..e121e3c3a 100644 --- a/YACReader/yacreader_zh_CN.ts +++ b/YACReader/yacreader_zh_CN.ts @@ -39,7 +39,7 @@ Remove - 消除 + 移除 @@ -59,7 +59,7 @@ Custom: - 自定义: + 自定义: @@ -136,7 +136,7 @@ Loading... - 载入中... + 加载中... @@ -145,7 +145,7 @@ - Lastest Page + Latest Page 尾页 @@ -176,41 +176,37 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function 快捷键 "%1" 已被映射至其他功能 - To change a shortcut, double click in the key combination and type the new keys. - 更改快捷键: 双击按键组合并输入新的映射. + To change a shortcut, double-click the key combination and type the new keys. + 更改快捷键:双击按键组合并输入新的映射。 FileComic - - + + Format not supported 不支持的文件格式 - + 7z not found 未找到 7z - + Unknown error opening the file 打开文件时出现未知错误 - Unsupported EPUB: %1 - 不支持的 EPUB 格式:%1 - - - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 页 CRC 校验失败: 部分页面将无法正确显示 + 第 %1 页 CRC 校验失败:部分页面将无法正确显示 @@ -228,8 +224,8 @@ - Total pages : - 总页数: + Total pages: + 总页数: @@ -238,16 +234,16 @@ - Page : - 页码 : + Page: + 页码: GoToFlowToolBar - Page : - 页码 : + Page: + 页码: @@ -278,7 +274,7 @@ Gamma - Gamma值 + Gamma 值 @@ -306,7 +302,7 @@ 我的漫画路径 - + Image adjustment 图像调整 @@ -333,7 +329,7 @@ Fit options - 适应项 + 适应选项 @@ -343,7 +339,7 @@ Display - 展示 + 显示 @@ -378,7 +374,7 @@ Only Back/Forward buttons can turn pages - 只有后退/前进按钮可以翻页 + 仅“后退”/“前进”按钮可用于翻页 @@ -387,7 +383,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. 单击屏幕的左半部分或右半部分即可翻页。 @@ -424,7 +420,7 @@ If none is active, Escape does nothing. 按 Esc 键会取消以下第一个处于活动状态的模式: 1. 放大镜 -2. 字典 +2. 词典 3. 跳转到页面栏 4. 全屏 @@ -451,12 +447,12 @@ If none is active, Escape does nothing. 显示书页折叠阴影 - + Appearance 外观 - + Options 选项 @@ -478,10 +474,10 @@ If none is active, Escape does nothing. Clear - 清空 + 清除 - + Comics directory 漫画目录 @@ -502,13 +498,13 @@ If none is active, Escape does nothing. - Scroll behaviour + Scroll behavior 滚动效果 Disable mouse over activation - 禁用鼠标激活 + 禁用鼠标悬停激活 @@ -528,7 +524,7 @@ If none is active, Escape does nothing. Bilinear - 双线性 + 双线性(Bilinear) @@ -536,12 +532,17 @@ If none is active, Escape does nothing. Lanczos(质量更好) - + + Bicubic + 双三次(Bicubic) + + + Page Flow 页面流 - + General 常规 @@ -551,7 +552,7 @@ If none is active, Escape does nothing. 亮度 - + Restart is needed 需要重启 @@ -566,15 +567,15 @@ If none is active, Escape does nothing. unable to load 7z lib from ./utils - 无法从 ./utils 载入 7z 库文件 + 无法从 ./utils 加载 7z 库文件 - + Select custom cover 选择自定义封面 - + Images (%1) 图片 (%1) @@ -609,7 +610,7 @@ If none is active, Escape does nothing. i - 我 + i @@ -624,7 +625,7 @@ If none is active, Escape does nothing. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - 按住可在 UI 中闪烁所选值(洋红色/切换/0↔10)。发布后恢复原样。 + 按住可在界面中闪烁所选值(洋红色/切换/0↔10)。松开后恢复原样。 @@ -654,7 +655,7 @@ If none is active, Escape does nothing. Variant: - 颜色设置: + 变体: @@ -664,7 +665,7 @@ If none is active, Escape does nothing. Parameter - 范围 + 参数 @@ -791,13 +792,13 @@ If none is active, Escape does nothing. Page not available! - 页面不可用! + 页面不可用! - Press 'O' to open comic. - 按下 'O' 以打开漫画. + Press 'O' to open a comic. + 按下 'O' 以打开漫画。 @@ -807,7 +808,7 @@ If none is active, Escape does nothing. Cover! - 封面! + 封面! @@ -827,12 +828,12 @@ If none is active, Escape does nothing. Last page! - 尾页! + 尾页! - Loading...please wait! - 载入中... 请稍候! + Loading... please wait! + 加载中...请稍候! @@ -840,7 +841,7 @@ If none is active, Escape does nothing. Presets: - 预设: + 预设: @@ -875,7 +876,7 @@ If none is active, Escape does nothing. Custom: - 自定义: + 自定义: @@ -920,12 +921,12 @@ If none is active, Escape does nothing. Visibility - 透明度 + 可见度 Light - 明亮 + 光照 @@ -944,13 +945,13 @@ If none is active, Escape does nothing. - Use VSync (improve the image quality in fullscreen mode, worse performance) - 使用VSync (在全屏模式下提高图像质量, 性能更差) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + 使用 VSync(在全屏模式下提高图像质量,但会降低性能) Performance: - 性能: + 性能: @@ -1004,7 +1005,7 @@ If none is active, Escape does nothing. Clear - 清空 + 清除 @@ -1012,7 +1013,7 @@ If none is active, Escape does nothing. 关闭 - + Open Comic 打开漫画 @@ -1024,12 +1025,12 @@ If none is active, Escape does nothing. Zoom+ - 放大 + 放大+ Zoom- - 缩小 + 缩小- @@ -1037,14 +1038,14 @@ If none is active, Escape does nothing. 打开图片文件夹 - + Size down magnifying glass 减小放大镜尺寸 - + Zoom out magnifying glass - 减小缩放级别 + 减小放大镜显示内容 @@ -1057,7 +1058,7 @@ If none is active, Escape does nothing. 打开最近的漫画 - + Autoscroll up 向上自动滚动 @@ -1067,7 +1068,7 @@ If none is active, Escape does nothing. 设置书签 - + Autoscroll forward, vertical first 向前自动滚动,垂直优先 @@ -1078,14 +1079,14 @@ If none is active, Escape does nothing. - - - + + + Save current page 保存当前页面 - + Size up magnifying glass 增大放大镜尺寸 @@ -1095,7 +1096,7 @@ If none is active, Escape does nothing. 双页模式 - + Move up 向上移动 @@ -1110,13 +1111,13 @@ If none is active, Escape does nothing. 打开文件夹 - - + + Comics 漫画 - + Offset double page to the right 双页向右偏移 @@ -1126,29 +1127,29 @@ If none is active, Escape does nothing. 适应高度 - + Autoscroll backward, vertical first 向后自动滚动,垂直优先 - + Comic files 漫画文件 - + Not now 现在不 - + Go to the first page 转到第一页 - - + + Go to previous page 转至上一页 @@ -1160,7 +1161,7 @@ If none is active, Escape does nothing. Open the latest comic opened in the previous reading session - 打开最近阅读漫画 + 打开上次阅读会话中最后打开的漫画 @@ -1175,7 +1176,7 @@ If none is active, Escape does nothing. Fit Width - 适合宽度 + 适应宽度 @@ -1188,19 +1189,21 @@ If none is active, Escape does nothing. 显示信息 - + Open folder 打开文件夹 - Go to page ... - 跳转至页面 ... + Go to page... + 跳转至页面... - - - Magnifiying glass + + + + Magnifying glass + Magnifiying glass 放大镜 @@ -1209,24 +1212,24 @@ If none is active, Escape does nothing. 缩放图片以适应宽度 - + Toggle fullscreen mode 切换全屏模式 - + Toggle between fit to width and fit to height - 切换显示为"适应宽度"或"适应高度" + 切换显示为“适应宽度”或“适应高度” - + Move right 向右移动 - + Zoom in magnifying glass - 增大缩放级别 + 增大放大镜显示内容 @@ -1235,13 +1238,13 @@ If none is active, Escape does nothing. 最近打开的文件 - + Offset double page to the left 双页向左偏移 - - + + Reading 阅读 @@ -1251,14 +1254,14 @@ If none is active, Escape does nothing. 上一页(&P) - + Autoscroll forward, horizontal first 向前自动滚动,水平优先 - - + + Go to next page 转至下一页 @@ -1273,24 +1276,24 @@ If none is active, Escape does nothing. 双页日漫模式 - + There is a new version available 有新版本可用 - + Autoscroll down 向下自动滚动 - - + + Open next comic 打开下一个漫画 - + Remind me in 14 days 14天后提醒我 @@ -1306,8 +1309,8 @@ If none is active, Escape does nothing. - - + + Open previous comic 打开上一个漫画 @@ -1323,10 +1326,10 @@ If none is active, Escape does nothing. - - - - + + + + Extract page(s) 提取页面 @@ -1368,72 +1371,72 @@ If none is active, Escape does nothing. Show Dictionary - 显示字典 + 显示词典 - + The current page is not ready. Please try again later. 当前页面尚未准备就绪。请稍后重试。 - + Overwrite file? 覆盖文件? - + The file already exists. Do you want to overwrite it? - 文件已存在。是​​否要覆盖它? + 文件已存在。是否要覆盖它? - + The current page could not be saved. 无法保存当前页面。 - + The selected pages are not ready. Please try again later. 所选页面尚未准备就绪。请稍后重试。 - + The current page could not be extracted. 无法提取当前页面。 - + Overwrite files? 覆盖文件? - + Some files already exist. Do you want to overwrite them? - 部分文件已存在。是​​否要覆盖它们? + 部分文件已存在。是否要覆盖它们? - + Some pages could not be extracted. 部分页面无法提取。 - + Reset magnifying glass 重置放大镜 - + Move down 向下移动 - + Move left 向左移动 Reverse reading order in double page mode - 双页模式 (逆序阅读) + 双页模式(逆序阅读) @@ -1443,12 +1446,12 @@ If none is active, Escape does nothing. Clear open recent list - 清空最近访问列表 + 清除最近打开列表 Help, About YACReader - 帮助, 关于 YACReader + 帮助,关于 YACReader @@ -1466,23 +1469,18 @@ If none is active, Escape does nothing. 显示全尺寸 - + Hide/show toolbar 隐藏/显示 工具栏 - - - Magnifying glass - 放大镜 - Edit shortcuts 编辑快捷键 - - + + General 常规 @@ -1492,9 +1490,10 @@ If none is active, Escape does nothing. 在当前页面设置书签 - - - Page adjustement + + + Page adjustment + Page adjustement 页面调整 @@ -1503,14 +1502,14 @@ If none is active, Escape does nothing. 显示缩放滑块 - + Go to the last page 转到最后一页 - + Do you want to download the new version? - 你要下载新版本吗? + 您要下载新版本吗? @@ -1518,7 +1517,7 @@ If none is active, Escape does nothing. 向右旋转图片 - + Autoscroll backward, horizontal first 向后自动滚动,水平优先 @@ -1528,12 +1527,12 @@ If none is active, Escape does nothing. Release notes are not available. - 暂无发行说明。 + 发行说明不可用。 Previous versions - 先前版本 + 以前的版本 @@ -1545,7 +1544,7 @@ If none is active, Escape does nothing. - + Click to overwrite 点击以覆盖 @@ -1559,9 +1558,9 @@ If none is active, Escape does nothing. - - - + + + Click to overwrite 点击以覆盖 @@ -1612,7 +1611,7 @@ If none is active, Escape does nothing. clear - 清空 + 清除 diff --git a/YACReader/yacreader_zh_HK.ts b/YACReader/yacreader_zh_HK.ts index c4eba1828..ccd1a4f51 100644 --- a/YACReader/yacreader_zh_HK.ts +++ b/YACReader/yacreader_zh_HK.ts @@ -24,22 +24,22 @@ Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 Custom - 風俗 + 自訂 Remove - 消除 + 移除 @@ -49,22 +49,22 @@ Light: - 光: + 淺色: Dark: - 黑暗的: + 深色: Custom: - 自定義: + 自訂: Import theme... - 導入主題... + 匯入主題... @@ -94,7 +94,7 @@ Import theme - 導入主題 + 匯入主題 @@ -122,14 +122,14 @@ Import failed - 導入失敗 + 匯入失敗 BookmarksDialog - Lastest Page + Latest Page 尾頁 @@ -162,17 +162,17 @@ Restore defaults - 恢復默認 + 恢復預設 - To change a shortcut, double click in the key combination and type the new keys. - 更改快捷鍵: 雙擊按鍵組合並輸入新的映射. + To change a shortcut, double-click the key combination and type the new keys. + 更改快捷鍵:雙擊按鍵組合並輸入新的映射。 Shortcuts settings - 快捷鍵設置 + 快捷鍵設定 @@ -181,40 +181,40 @@ - The shortcut "%1" is already assigned to other function - 快捷鍵 "%1" 已被映射至其他功能 + The shortcut "%1" is already assigned to another function + 快捷鍵「%1」已被映射至其他功能 FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 頁 CRC 校驗失敗: 部分頁面將無法正確顯示 + 第 %1 頁 CRC 校驗失敗:部分頁面將無法正確顯示 - + Unknown error opening the file - 打開檔時出現未知錯誤 + 開啟檔案時出現未知錯誤 - + 7z not found 未找到 7z - - + + Format not supported - 不支持的檔格式 + 不支援的檔案格式 GoToDialog - Page : - 頁碼 : + Page: + 頁碼: @@ -229,8 +229,8 @@ - Total pages : - 總頁數: + Total pages: + 總頁數: @@ -242,8 +242,8 @@ GoToFlowToolBar - Page : - 頁碼 : + Page: + 頁碼: @@ -266,7 +266,7 @@ Changelog - + 更新日誌 @@ -299,7 +299,7 @@ Disable mouse over activation - 禁用滑鼠啟動 + 停用滑鼠懸停啟動 @@ -319,7 +319,7 @@ Bilinear - 雙線性 + Bilinear @@ -327,7 +327,7 @@ Lanczos(品質更好) - + Restart is needed 需要重啟 @@ -339,7 +339,7 @@ Display - 展示 + 顯示 @@ -368,7 +368,7 @@ - Scroll behaviour + Scroll behavior 滾動效果 @@ -403,7 +403,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. 點擊螢幕的左半部或右半部即可翻頁。 @@ -440,7 +440,7 @@ If none is active, Escape does nothing. 按 Esc 鍵會取消下列第一個正在使用的模式: 1. 放大鏡 -2. 字典 +2. 詞典 3. 前往頁面列 4. 全螢幕 @@ -464,12 +464,12 @@ If none is active, Escape does nothing. Gamma - Gamma值 + Gamma 值 Reset - 重置 + 重設 @@ -479,7 +479,7 @@ If none is active, Escape does nothing. Fit options - 適應項 + 適應選項 @@ -502,14 +502,19 @@ If none is active, Escape does nothing. 顯示書頁摺疊陰影 - + + Bicubic + + + + General 常規 - + Appearance - 外貌 + 外觀 @@ -532,22 +537,22 @@ If none is active, Escape does nothing. 清空 - + Page Flow 頁面流 - + Image adjustment - 圖像調整 + 影像調整 - + Options 選項 - + Comics directory 漫畫目錄 @@ -557,20 +562,20 @@ If none is active, Escape does nothing. 7z lib not found - 未找到 7z 庫檔 + 找不到 7z 函式庫 unable to load 7z lib from ./utils - 無法從 ./utils 載入 7z 庫檔 + 無法從 ./utils 載入 7z 函式庫 - + Select custom cover 選擇自訂封面 - + Images (%1) 圖片 (%1) @@ -605,7 +610,7 @@ If none is active, Escape does nothing. i - 我 + i @@ -620,7 +625,7 @@ If none is active, Escape does nothing. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。發布後恢復原樣。 + 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。放開後恢復原樣。 @@ -630,12 +635,12 @@ If none is active, Escape does nothing. Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 @@ -655,27 +660,27 @@ If none is active, Escape does nothing. Theme info - 主題訊息 + 主題資訊 Parameter - 範圍 + 參數 Value - 價值 + 值 Save and apply - 儲存並應用 + 儲存並套用 Export to file... - 匯出到文件... + 匯出到檔案... @@ -700,7 +705,7 @@ If none is active, Escape does nothing. true - 真的 + true @@ -708,12 +713,12 @@ If none is active, Escape does nothing. false - 錯誤的 + false Double-click to toggle - 按兩下切換 + 雙擊切換 @@ -741,13 +746,13 @@ If none is active, Escape does nothing. Save failed - 保存失敗 + 儲存失敗 Could not open file for writing: %1 - 無法開啟文件進行寫入: + 無法開啟檔案進行寫入: %1 @@ -787,8 +792,8 @@ If none is active, Escape does nothing. - Press 'O' to open comic. - 按下 'O' 以打開漫畫. + Press 'O' to open a comic. + 按下 'O' 以開啟漫畫。 @@ -803,7 +808,7 @@ If none is active, Escape does nothing. Error opening comic - 打開漫畫時發生錯誤 + 開啟漫畫時發生錯誤 @@ -812,23 +817,23 @@ If none is active, Escape does nothing. - Loading...please wait! - 載入中... 請稍候! + Loading... please wait! + 載入中... 請稍候! Page not available! - 頁面不可用! + 頁面不可用! Cover! - 封面! + 封面! Last page! - 尾頁! + 尾頁! @@ -836,7 +841,7 @@ If none is active, Escape does nothing. Presets: - 預設: + 預設: @@ -866,12 +871,12 @@ If none is active, Escape does nothing. Show advanced settings - 顯示高級選項 + 顯示進階選項 Custom: - 自定義: + 自訂: @@ -896,7 +901,7 @@ If none is active, Escape does nothing. Zoom - 縮放 + Zoom @@ -916,12 +921,12 @@ If none is active, Escape does nothing. Visibility - 透明度 + 可見度 Light - 亮度 + 光照 @@ -931,22 +936,22 @@ If none is active, Escape does nothing. Low Performance - 低性能 + 低效能 High Performance - 高性能 + 高效能 - Use VSync (improve the image quality in fullscreen mode, worse performance) - 使用VSync (在全屏模式下提高圖像品質, 性能更差) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + 使用 VSync(在全螢幕模式下提高影像品質,但會降低效能) Performance: - 性能: + 效能: @@ -954,12 +959,12 @@ If none is active, Escape does nothing. &Open - 打開(&O) + 開啟(&O) Open a comic - 打開漫畫 + 開啟漫畫 @@ -969,22 +974,22 @@ If none is active, Escape does nothing. Open Folder - 打開檔夾 + 開啟資料夾 Open image folder - 打開圖片檔夾 + 開啟圖片資料夾 Open latest comic - 打開最近的漫畫 + 開啟最近的漫畫 Open the latest comic opened in the previous reading session - 打開最近閱讀漫畫 + 開啟上次閱讀工作階段中最後開啟的漫畫 @@ -994,34 +999,34 @@ If none is active, Escape does nothing. Clear open recent list - 清空最近訪問列表 + 清除最近開啟清單 Save - 保存 + 儲存 - - - + + + Save current page - 保存當前頁面 + 儲存目前頁面 - - - - + + + + Extract page(s) - + 擷取頁面 Extract page(s) from the original source - + 從原始來源擷取頁面 @@ -1030,10 +1035,10 @@ If none is active, Escape does nothing. - - + + Open previous comic - 打開上一個漫畫 + 開啟上一個漫畫 @@ -1042,10 +1047,10 @@ If none is active, Escape does nothing. - - + + Open next comic - 打開下一個漫畫 + 開啟下一個漫畫 @@ -1054,8 +1059,8 @@ If none is active, Escape does nothing. - - + + Go to previous page 轉至上一頁 @@ -1066,8 +1071,8 @@ If none is active, Escape does nothing. - - + + Go to next page 轉至下一頁 @@ -1084,7 +1089,7 @@ If none is active, Escape does nothing. Fit Width - 適合寬度 + 適應寬度 @@ -1114,22 +1119,22 @@ If none is active, Escape does nothing. Reset zoom - 重置縮放 + 重設 Zoom Show zoom slider - 顯示縮放滑塊 + 顯示 Zoom 滑桿 Zoom+ - 放大 + Zoom+ Zoom- - 縮小 + Zoom- @@ -1159,7 +1164,7 @@ If none is active, Escape does nothing. Reverse reading order in double page mode - 雙頁模式 (逆序閱讀) + 雙頁模式(逆序閱讀) @@ -1168,8 +1173,8 @@ If none is active, Escape does nothing. - Go to page ... - 跳轉至頁面 ... + Go to page... + 跳轉至頁面... @@ -1190,10 +1195,12 @@ If none is active, Escape does nothing. Help, About YACReader - 幫助, 關於 YACReader + 幫助,關於 YACReader + + Magnifying glass 放大鏡 @@ -1205,12 +1212,12 @@ If none is active, Escape does nothing. Set bookmark - 設置書簽 + 設定書簽 Set a bookmark on the current page - 在當前頁面設置書簽 + 在目前頁面設定書簽 @@ -1220,7 +1227,7 @@ If none is active, Escape does nothing. Show the bookmarks of the current comic - 顯示當前漫畫的書簽 + 顯示目前漫畫的書簽 @@ -1250,7 +1257,7 @@ If none is active, Escape does nothing. Show Dictionary - 顯示字典 + 顯示詞典 @@ -1265,18 +1272,18 @@ If none is active, Escape does nothing. &File - 檔(&F) + 檔案(&F) Open recent - 最近打開的檔 + 最近開啟的檔案 File - 檔 + 檔案 @@ -1286,7 +1293,7 @@ If none is active, Escape does nothing. View - 查看 + 檢視 @@ -1296,225 +1303,220 @@ If none is active, Escape does nothing. Window - 窗口 + 視窗 - + Open Comic - 打開漫畫 + 開啟漫畫 - + Comic files - 漫畫檔 + 漫畫檔案 - + Open folder - 打開檔夾 + 開啟資料夾 - - + + Comics 漫畫 - + Toggle fullscreen mode - 切換全屏模式 + 切換全螢幕模式 - + Hide/show toolbar - 隱藏/顯示 工具欄 + 隱藏/顯示工具列 - - + + General 常規 - + Size up magnifying glass 增大放大鏡尺寸 - + Size down magnifying glass 減小放大鏡尺寸 - + Zoom in magnifying glass - 增大縮放級別 + 增大放大鏡的 Zoom - + Zoom out magnifying glass - 減小縮放級別 + 減小放大鏡的 Zoom - + Reset magnifying glass 重置放大鏡 - - - Magnifiying glass - 放大鏡 - - - + Toggle between fit to width and fit to height - 切換顯示為"適應寬度"或"適應高度" + 切換顯示為「適應寬度」或「適應高度」 - - - Page adjustement + + + Page adjustment + Page adjustement 頁面調整 - + The current page is not ready. Please try again later. 目前頁面尚未準備就緒。請稍後再試。 - + Overwrite file? - + 要覆寫檔案嗎? - + The file already exists. Do you want to overwrite it? - + 檔案已存在。要覆寫嗎? - + The current page could not be saved. 無法儲存目前頁面。 - + The selected pages are not ready. Please try again later. 所選頁面尚未準備就緒。請稍後再試。 - + The current page could not be extracted. - + 無法擷取目前頁面。 - + Overwrite files? - + 要覆寫這些檔案嗎? - + Some files already exist. Do you want to overwrite them? - + 部分檔案已存在。要覆寫它們嗎? - + Some pages could not be extracted. - + 部分頁面無法擷取。 - + Autoscroll down 向下自動滾動 - + Autoscroll up 向上自動滾動 - + Autoscroll forward, horizontal first - 向前自動滾動,水準優先 + 向前自動滾動,水平優先 - + Autoscroll backward, horizontal first - 向後自動滾動,水準優先 + 向後自動滾動,水平優先 - + Autoscroll forward, vertical first 向前自動滾動,垂直優先 - + Autoscroll backward, vertical first 向後自動滾動,垂直優先 - + Move down 向下移動 - + Move up 向上移動 - + Move left 向左移動 - + Move right 向右移動 - + Go to the first page 轉到第一頁 - + Go to the last page 轉到最後一頁 - + Offset double page to the left 雙頁向左偏移 - + Offset double page to the right 雙頁向右偏移 - - + + Reading 閱讀 - + There is a new version available 有新版本可用 - + Do you want to download the new version? - 你要下載新版本嗎? + 您要下載新版本嗎? - + Remind me in 14 days 14天後提醒我 - + Not now 現在不 @@ -1524,42 +1526,42 @@ If none is active, Escape does nothing. Release notes are not available. - + 版本資訊無法使用。 Previous versions - + 舊版本 YACReaderFieldEdit - + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 YACReaderFieldPlainTextEdit - - - + + + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 @@ -1567,7 +1569,7 @@ If none is active, Escape does nothing. Save - 保存 + 儲存 @@ -1600,7 +1602,7 @@ If none is active, Escape does nothing. Reset - 重置 + 重設 diff --git a/YACReader/yacreader_zh_TW.ts b/YACReader/yacreader_zh_TW.ts index 310ed55e5..60e186948 100644 --- a/YACReader/yacreader_zh_TW.ts +++ b/YACReader/yacreader_zh_TW.ts @@ -24,22 +24,22 @@ Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 Custom - 風俗 + 自訂 Remove - 消除 + 移除 @@ -49,22 +49,22 @@ Light: - 光: + 淺色: Dark: - 黑暗的: + 深色: Custom: - 自定義: + 自訂: Import theme... - 導入主題... + 匯入主題... @@ -94,7 +94,7 @@ Import theme - 導入主題 + 匯入主題 @@ -122,14 +122,14 @@ Import failed - 導入失敗 + 匯入失敗 BookmarksDialog - Lastest Page + Latest Page 尾頁 @@ -162,59 +162,59 @@ Restore defaults - 恢復默認 + 恢復預設 - To change a shortcut, double click in the key combination and type the new keys. - 更改快捷鍵: 雙擊按鍵組合並輸入新的映射. + To change a shortcut, double-click the key combination and type the new keys. + 更改快速鍵:雙擊按鍵組合並輸入新的映射。 Shortcuts settings - 快捷鍵設置 + 快速鍵設定 Shortcut in use - 快捷鍵被佔用 + 快速鍵被佔用 - The shortcut "%1" is already assigned to other function - 快捷鍵 "%1" 已被映射至其他功能 + The shortcut "%1" is already assigned to another function + 快速鍵「%1」已被映射至其他功能 FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 頁 CRC 校驗失敗: 部分頁面將無法正確顯示 + 第 %1 頁 CRC 校驗失敗:部分頁面將無法正確顯示 - + Unknown error opening the file - 打開檔時出現未知錯誤 + 開啟檔案時出現未知錯誤 - + 7z not found 未找到 7z - - + + Format not supported - 不支持的檔格式 + 不支援的檔案格式 GoToDialog - Page : - 頁碼 : + Page: + 頁碼: @@ -229,8 +229,8 @@ - Total pages : - 總頁數: + Total pages: + 總頁數: @@ -242,8 +242,8 @@ GoToFlowToolBar - Page : - 頁碼 : + Page: + 頁碼: @@ -266,7 +266,7 @@ Changelog - + 更新日誌 @@ -299,7 +299,7 @@ Disable mouse over activation - 禁用滑鼠啟動 + 停用滑鼠懸停啟動 @@ -319,7 +319,7 @@ Bilinear - 雙線性 + Bilinear @@ -327,7 +327,7 @@ Lanczos(品質更好) - + Restart is needed 需要重啟 @@ -339,7 +339,7 @@ Display - 展示 + 顯示 @@ -368,7 +368,7 @@ - Scroll behaviour + Scroll behavior 滾動效果 @@ -403,7 +403,7 @@ - Click left or right half of the screen to turn pages. + Click the left or right half of the screen to turn pages. 點擊螢幕的左半部或右半部即可翻頁。 @@ -440,7 +440,7 @@ If none is active, Escape does nothing. 按 Esc 鍵會取消下列第一個正在使用的模式: 1. 放大鏡 -2. 字典 +2. 詞典 3. 前往頁面列 4. 全螢幕 @@ -464,12 +464,12 @@ If none is active, Escape does nothing. Gamma - Gamma值 + Gamma 值 Reset - 重置 + 重設 @@ -479,7 +479,7 @@ If none is active, Escape does nothing. Fit options - 適應項 + 適應選項 @@ -502,14 +502,19 @@ If none is active, Escape does nothing. 顯示書頁折疊陰影 - + + Bicubic + + + + General 常規 - + Appearance - 外貌 + 外觀 @@ -532,22 +537,22 @@ If none is active, Escape does nothing. 清空 - + Page Flow 頁面流 - + Image adjustment - 圖像調整 + 影像調整 - + Options 選項 - + Comics directory 漫畫目錄 @@ -557,20 +562,20 @@ If none is active, Escape does nothing. 7z lib not found - 未找到 7z 庫檔 + 找不到 7z 函式庫 unable to load 7z lib from ./utils - 無法從 ./utils 載入 7z 庫檔 + 無法從 ./utils 載入 7z 函式庫 - + Select custom cover 選擇自訂封面 - + Images (%1) 圖片 (%1) @@ -605,7 +610,7 @@ If none is active, Escape does nothing. i - 我 + i @@ -620,7 +625,7 @@ If none is active, Escape does nothing. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。發布後恢復原樣。 + 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。放開後恢復原樣。 @@ -630,12 +635,12 @@ If none is active, Escape does nothing. Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 @@ -655,27 +660,27 @@ If none is active, Escape does nothing. Theme info - 主題訊息 + 主題資訊 Parameter - 範圍 + 參數 Value - 價值 + 值 Save and apply - 儲存並應用 + 儲存並套用 Export to file... - 匯出到文件... + 匯出到檔案... @@ -700,7 +705,7 @@ If none is active, Escape does nothing. true - 真的 + true @@ -708,12 +713,12 @@ If none is active, Escape does nothing. false - 錯誤的 + false Double-click to toggle - 按兩下切換 + 雙擊切換 @@ -741,13 +746,13 @@ If none is active, Escape does nothing. Save failed - 保存失敗 + 儲存失敗 Could not open file for writing: %1 - 無法開啟文件進行寫入: + 無法開啟檔案進行寫入: %1 @@ -787,8 +792,8 @@ If none is active, Escape does nothing. - Press 'O' to open comic. - 按下 'O' 以打開漫畫. + Press 'O' to open a comic. + 按下 'O' 以開啟漫畫。 @@ -803,7 +808,7 @@ If none is active, Escape does nothing. Error opening comic - 打開漫畫時發生錯誤 + 開啟漫畫時發生錯誤 @@ -812,23 +817,23 @@ If none is active, Escape does nothing. - Loading...please wait! - 載入中... 請稍候! + Loading... please wait! + 載入中... 請稍候! Page not available! - 頁面不可用! + 頁面不可用! Cover! - 封面! + 封面! Last page! - 尾頁! + 尾頁! @@ -836,7 +841,7 @@ If none is active, Escape does nothing. Presets: - 預設: + 預設: @@ -866,12 +871,12 @@ If none is active, Escape does nothing. Show advanced settings - 顯示高級選項 + 顯示進階選項 Custom: - 自定義: + 自訂: @@ -896,7 +901,7 @@ If none is active, Escape does nothing. Zoom - 縮放 + Zoom @@ -916,12 +921,12 @@ If none is active, Escape does nothing. Visibility - 透明度 + 可見度 Light - 亮度 + 光照 @@ -931,22 +936,22 @@ If none is active, Escape does nothing. Low Performance - 低性能 + 低效能 High Performance - 高性能 + 高效能 - Use VSync (improve the image quality in fullscreen mode, worse performance) - 使用VSync (在全屏模式下提高圖像品質, 性能更差) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + 使用 VSync(在全螢幕模式下提高影像品質,但會降低效能) Performance: - 性能: + 效能: @@ -954,12 +959,12 @@ If none is active, Escape does nothing. &Open - 打開(&O) + 開啟(&O) Open a comic - 打開漫畫 + 開啟漫畫 @@ -969,22 +974,22 @@ If none is active, Escape does nothing. Open Folder - 打開檔夾 + 開啟資料夾 Open image folder - 打開圖片檔夾 + 開啟圖片資料夾 Open latest comic - 打開最近的漫畫 + 開啟最近的漫畫 Open the latest comic opened in the previous reading session - 打開最近閱讀漫畫 + 開啟上次閱讀工作階段中最後開啟的漫畫 @@ -994,34 +999,34 @@ If none is active, Escape does nothing. Clear open recent list - 清空最近訪問列表 + 清除最近開啟清單 Save - 保存 + 儲存 - - - + + + Save current page - 保存當前頁面 + 儲存目前頁面 - - - - + + + + Extract page(s) - + 擷取頁面 Extract page(s) from the original source - + 從原始來源擷取頁面 @@ -1030,10 +1035,10 @@ If none is active, Escape does nothing. - - + + Open previous comic - 打開上一個漫畫 + 開啟上一個漫畫 @@ -1042,10 +1047,10 @@ If none is active, Escape does nothing. - - + + Open next comic - 打開下一個漫畫 + 開啟下一個漫畫 @@ -1054,8 +1059,8 @@ If none is active, Escape does nothing. - - + + Go to previous page 轉至上一頁 @@ -1066,8 +1071,8 @@ If none is active, Escape does nothing. - - + + Go to next page 轉至下一頁 @@ -1084,7 +1089,7 @@ If none is active, Escape does nothing. Fit Width - 適合寬度 + 適應寬度 @@ -1114,22 +1119,22 @@ If none is active, Escape does nothing. Reset zoom - 重置縮放 + 重設 Zoom Show zoom slider - 顯示縮放滑塊 + 顯示 Zoom 滑桿 Zoom+ - 放大 + Zoom+ Zoom- - 縮小 + Zoom- @@ -1159,7 +1164,7 @@ If none is active, Escape does nothing. Reverse reading order in double page mode - 雙頁模式 (逆序閱讀) + 雙頁模式(逆序閱讀) @@ -1168,8 +1173,8 @@ If none is active, Escape does nothing. - Go to page ... - 跳轉至頁面 ... + Go to page... + 跳轉至頁面... @@ -1190,10 +1195,12 @@ If none is active, Escape does nothing. Help, About YACReader - 幫助, 關於 YACReader + 幫助,關於 YACReader + + Magnifying glass 放大鏡 @@ -1205,12 +1212,12 @@ If none is active, Escape does nothing. Set bookmark - 設置書簽 + 設定書簽 Set a bookmark on the current page - 在當前頁面設置書簽 + 在目前頁面設定書簽 @@ -1220,12 +1227,12 @@ If none is active, Escape does nothing. Show the bookmarks of the current comic - 顯示當前漫畫的書簽 + 顯示目前漫畫的書簽 Show keyboard shortcuts - 顯示鍵盤快捷鍵 + 顯示鍵盤快速鍵 @@ -1250,7 +1257,7 @@ If none is active, Escape does nothing. Show Dictionary - 顯示字典 + 顯示詞典 @@ -1260,23 +1267,23 @@ If none is active, Escape does nothing. Edit shortcuts - 編輯快捷鍵 + 編輯快速鍵 &File - 檔(&F) + 檔案(&F) Open recent - 最近打開的檔 + 最近開啟的檔案 File - 檔 + 檔案 @@ -1286,7 +1293,7 @@ If none is active, Escape does nothing. View - 查看 + 檢視 @@ -1296,225 +1303,220 @@ If none is active, Escape does nothing. Window - 窗口 + 視窗 - + Open Comic - 打開漫畫 + 開啟漫畫 - + Comic files - 漫畫檔 + 漫畫檔案 - + Open folder - 打開檔夾 + 開啟資料夾 - - + + Comics 漫畫 - + Toggle fullscreen mode - 切換全屏模式 + 切換全螢幕模式 - + Hide/show toolbar - 隱藏/顯示 工具欄 + 隱藏/顯示工具列 - - + + General 常規 - + Size up magnifying glass 增大放大鏡尺寸 - + Size down magnifying glass 減小放大鏡尺寸 - + Zoom in magnifying glass - 增大縮放級別 + 增大放大鏡的 Zoom - + Zoom out magnifying glass - 減小縮放級別 + 減小放大鏡的 Zoom - + Reset magnifying glass 重置放大鏡 - - - Magnifiying glass - 放大鏡 - - - + Toggle between fit to width and fit to height - 切換顯示為"適應寬度"或"適應高度" + 切換顯示為「適應寬度」或「適應高度」 - - - Page adjustement + + + Page adjustment + Page adjustement 頁面調整 - + The current page is not ready. Please try again later. 目前頁面尚未準備就緒。請稍後再試。 - + Overwrite file? - + 要覆寫檔案嗎? - + The file already exists. Do you want to overwrite it? - + 檔案已存在。要覆寫嗎? - + The current page could not be saved. 無法儲存目前頁面。 - + The selected pages are not ready. Please try again later. 所選頁面尚未準備就緒。請稍後再試。 - + The current page could not be extracted. - + 無法擷取目前頁面。 - + Overwrite files? - + 要覆寫這些檔案嗎? - + Some files already exist. Do you want to overwrite them? - + 部分檔案已存在。要覆寫它們嗎? - + Some pages could not be extracted. - + 部分頁面無法擷取。 - + Autoscroll down 向下自動滾動 - + Autoscroll up 向上自動滾動 - + Autoscroll forward, horizontal first - 向前自動滾動,水準優先 + 向前自動滾動,水平優先 - + Autoscroll backward, horizontal first - 向後自動滾動,水準優先 + 向後自動滾動,水平優先 - + Autoscroll forward, vertical first 向前自動滾動,垂直優先 - + Autoscroll backward, vertical first 向後自動滾動,垂直優先 - + Move down 向下移動 - + Move up 向上移動 - + Move left 向左移動 - + Move right 向右移動 - + Go to the first page 轉到第一頁 - + Go to the last page 轉到最後一頁 - + Offset double page to the left 雙頁向左偏移 - + Offset double page to the right 雙頁向右偏移 - - + + Reading 閱讀 - + There is a new version available 有新版本可用 - + Do you want to download the new version? - 你要下載新版本嗎? + 您要下載新版本嗎? - + Remind me in 14 days 14天後提醒我 - + Not now 現在不 @@ -1524,42 +1526,42 @@ If none is active, Escape does nothing. Release notes are not available. - + 版本資訊無法使用。 Previous versions - + 舊版本 YACReaderFieldEdit - + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 YACReaderFieldPlainTextEdit - - - + + + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 @@ -1567,7 +1569,7 @@ If none is active, Escape does nothing. Save - 保存 + 儲存 @@ -1587,12 +1589,12 @@ If none is active, Escape does nothing. Edit shortcuts - 編輯快捷鍵 + 編輯快速鍵 Shortcuts - 快捷鍵 + 快速鍵 @@ -1600,7 +1602,7 @@ If none is active, Escape does nothing. Reset - 重置 + 重設 diff --git a/YACReaderLibrary/CMakeLists.txt b/YACReaderLibrary/CMakeLists.txt index 8641fc102..7efb46aca 100644 --- a/YACReaderLibrary/CMakeLists.txt +++ b/YACReaderLibrary/CMakeLists.txt @@ -24,6 +24,16 @@ add_library(library_common STATIC yacreader_libraries.cpp comic_files_manager.h comic_files_manager.cpp + staged_cover.h + staged_cover.cpp + placeholder_cover.h + placeholder_cover.cpp + library_comic_match.h + library_comic_match.cpp + folder_placeholder_match.h + folder_placeholder_match.cpp + tracked_issues.h + tracked_issues.cpp ip_config_helper.h ip_config_helper.cpp libraries_update_coordinator.h @@ -54,6 +64,16 @@ add_library(db_helper STATIC db/data_base_management.cpp db/reading_list.h db/reading_list.cpp + db/list_entry.h + db/list_entry.cpp + db/placeholder.h + db/placeholder.cpp + db/comic_item.h + db/comic_item.cpp + db/smart_list.h + db/smart_list.cpp + db/list_folder.h + db/list_folder.cpp db/query_lexer.h db/query_lexer.cpp db/search_field_registry.h @@ -98,6 +118,7 @@ qt_add_executable(YACReaderLibrary WIN32 folder_management_coordinator.cpp library_database_maintenance_coordinator.h library_database_maintenance_coordinator.cpp + library_maintenance_dialog.h library_repair_coordinator.h library_repair_coordinator.cpp library_management_coordinator.h @@ -115,6 +136,20 @@ qt_add_executable(YACReaderLibrary WIN32 options_dialog.cpp search_syntax_dialog.h search_syntax_dialog.cpp + search_filters_menu.h + search_filters_menu.cpp + smart_list_dialog.h + smart_list_dialog.cpp + new_list_folder_dialog.h + new_list_folder_dialog.cpp + move_to_folder_dialog.h + move_to_folder_dialog.cpp + reading_list_page.h + reading_list_page.cpp + delayed_progress_dialog.h + delayed_progress_dialog.cpp + empty_list_folder_widget.h + empty_list_folder_widget.cpp export_library_dialog.h export_library_dialog.cpp import_library_dialog.h @@ -135,10 +170,10 @@ qt_add_executable(YACReaderLibrary WIN32 trayicon_controller.cpp yacreader_content_views_manager.h yacreader_content_views_manager.cpp - yacreader_main_toolbar.h - yacreader_main_toolbar.cpp comics_view.h comics_view.cpp + comic_info_object.h + comic_info_object.cpp content_view_state.h comics_view_transition.h comics_view_transition.cpp @@ -164,8 +199,7 @@ qt_add_executable(YACReaderLibrary WIN32 db/folder_model.cpp db/comic_model.h db/comic_model.cpp - db/comic_item.h - db/comic_item.cpp + db/comic_record.h db/reading_list_model.h db/reading_list_model.cpp db/reading_list_item.h @@ -215,6 +249,28 @@ if(APPLE) ) endif() +target_sources(YACReaderLibrary PRIVATE + library_header/header_model.h + library_header/header_model.cpp + library_header/library_header.h + library_header/library_header.cpp + library_header/view_selector.h + library_header/view_selector.cpp + library_header/window_frame.h + library_header/window_frame.cpp +) +if(WIN32) + target_sources(YACReaderLibrary PRIVATE + library_header/caption_buttons.h + library_header/caption_buttons.cpp + library_header/window_frame_windows.cpp + ) +endif() +if(APPLE) + target_sources(YACReaderLibrary PRIVATE library_header/window_frame_macos.mm) + set_source_files_properties(library_header/window_frame_macos.mm PROPERTIES COMPILE_OPTIONS "-fobjc-arc") +endif() + target_include_directories(YACReaderLibrary PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/db @@ -306,6 +362,10 @@ set(yacreaderlibrary_image_files ${PROJECT_SOURCE_DIR}/images/noLibrariesIcon.svg ${PROJECT_SOURCE_DIR}/images/noLibrariesLine.svg ${PROJECT_SOURCE_DIR}/images/notCover.png + ${PROJECT_SOURCE_DIR}/images/unavailableCover.png + ${PROJECT_SOURCE_DIR}/images/unavailableCover.svg + ${PROJECT_SOURCE_DIR}/images/comics_view_table/deletedComic.svg + ${PROJECT_SOURCE_DIR}/images/comics_view_table/placeholderComic.svg ${PROJECT_SOURCE_DIR}/images/library_dialogs/openLibrary.svg ${PROJECT_SOURCE_DIR}/images/metadata_dialog/previousCoverPage.svg ${PROJECT_SOURCE_DIR}/images/readingRibbon.svg @@ -346,6 +406,8 @@ set(yacreaderlibrary_image_files ${PROJECT_SOURCE_DIR}/images/lists/default_2.svg ${PROJECT_SOURCE_DIR}/images/lists/label_template.svg ${PROJECT_SOURCE_DIR}/images/lists/list.svg + ${PROJECT_SOURCE_DIR}/images/lists/smart_list.svg + ${PROJECT_SOURCE_DIR}/images/empty_container/smart_list_card.svg ${PROJECT_SOURCE_DIR}/images/clearSearchNew.svg ${PROJECT_SOURCE_DIR}/images/iconSearchNew.svg ) @@ -371,6 +433,7 @@ set(yacreaderlibrary_qml_files ${CMAKE_CURRENT_SOURCE_DIR}/qml/EmptyInfoView.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/FolderCover.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/FolderGridDelegate.qml + ${CMAKE_CURRENT_SOURCE_DIR}/qml/ListGridDelegate.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/FolderInfoView.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/SelectedComicsInfoView.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/LibraryInfoView.qml @@ -401,6 +464,7 @@ set(yacreaderlibrary_qml_translation_files ${CMAKE_CURRENT_SOURCE_DIR}/qml/EmptyInfoView.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/FolderCover.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/FolderGridDelegate.qml + ${CMAKE_CURRENT_SOURCE_DIR}/qml/ListGridDelegate.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/FolderInfoView.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/SelectedComicsInfoView.qml ${CMAKE_CURRENT_SOURCE_DIR}/qml/LibraryInfoView.qml @@ -503,22 +567,29 @@ if(APPLE) endif() # Translations +set(yacreaderlibrary_translation_source_targets + YACReaderLibrary + library_common + db_helper + comic_backend + common_gui + custom_widgets_library + shortcuts_library + comic_vine + organize_files + cbl + cbl_gui +) qt_add_translations(YACReaderLibrary - SOURCE_TARGETS - YACReaderLibrary - db_helper - comic_backend - common_gui - custom_widgets_library - shortcuts_library - comic_vine - organize_files + SOURCE_TARGETS ${yacreaderlibrary_translation_source_targets} # Keep extraction scoped to targets used by this app and add the QML files # directly so qsTr() strings in QML are collected too. + LUPDATE_OPTIONS -no-obsolete TS_FILES yacreaderlibrary_es.ts yacreaderlibrary_ru.ts yacreaderlibrary_pt.ts + yacreaderlibrary_pt_PT.ts yacreaderlibrary_fr.ts yacreaderlibrary_nl.ts yacreaderlibrary_tr.ts @@ -528,13 +599,20 @@ qt_add_translations(YACReaderLibrary yacreaderlibrary_zh_HK.ts yacreaderlibrary_it.ts yacreaderlibrary_ko.ts - yacreaderlibrary_source.ts yacreaderlibrary_en.ts SOURCES ${yacreaderlibrary_qml_translation_files} ) +qt_add_lupdate( + SOURCE_TARGETS ${yacreaderlibrary_translation_source_targets} + OPTIONS -no-obsolete + TS_FILES yacreaderlibrary_source.ts + SOURCES ${yacreaderlibrary_qml_translation_files} + LUPDATE_TARGET YACReaderLibrary_source_lupdate +) target_link_libraries(YACReaderLibrary PRIVATE + Qt6::Concurrent Qt6::Core Qt6::Gui Qt6::GuiPrivate @@ -556,6 +634,7 @@ target_link_libraries(YACReaderLibrary PRIVATE server comic_vine organize_files + cbl_gui cbx_backend concurrent_queue worker @@ -578,9 +657,9 @@ endif() if(WIN32) target_sources(YACReaderLibrary PRIVATE icon.rc) if(MSVC) - target_link_options(YACReaderLibrary PRIVATE "/MANIFESTINPUT:${PROJECT_SOURCE_DIR}/cmake/windows/yacreader.manifest") + target_sources(YACReaderLibrary PRIVATE ${PROJECT_SOURCE_DIR}/cmake/windows/yacreader.manifest) endif() - target_link_libraries(YACReaderLibrary PRIVATE oleaut32 ole32 shell32 user32) + target_link_libraries(YACReaderLibrary PRIVATE oleaut32 ole32 shell32 user32 dwmapi) endif() if(APPLE) diff --git a/YACReaderLibrary/add_label_dialog.cpp b/YACReaderLibrary/add_label_dialog.cpp index b8399a847..acb86d27e 100644 --- a/YACReaderLibrary/add_label_dialog.cpp +++ b/YACReaderLibrary/add_label_dialog.cpp @@ -5,18 +5,18 @@ namespace { const char *labelColorNames[] = { - QT_TR_NOOP("red"), - QT_TR_NOOP("orange"), - QT_TR_NOOP("yellow"), - QT_TR_NOOP("green"), - QT_TR_NOOP("cyan"), - QT_TR_NOOP("blue"), - QT_TR_NOOP("violet"), - QT_TR_NOOP("purple"), - QT_TR_NOOP("pink"), - QT_TR_NOOP("white"), - QT_TR_NOOP("light"), - QT_TR_NOOP("dark") + QT_TRANSLATE_NOOP("AddLabelDialog", "red"), + QT_TRANSLATE_NOOP("AddLabelDialog", "orange"), + QT_TRANSLATE_NOOP("AddLabelDialog", "yellow"), + QT_TRANSLATE_NOOP("AddLabelDialog", "green"), + QT_TRANSLATE_NOOP("AddLabelDialog", "cyan"), + QT_TRANSLATE_NOOP("AddLabelDialog", "blue"), + QT_TRANSLATE_NOOP("AddLabelDialog", "violet"), + QT_TRANSLATE_NOOP("AddLabelDialog", "purple"), + QT_TRANSLATE_NOOP("AddLabelDialog", "pink"), + QT_TRANSLATE_NOOP("AddLabelDialog", "white"), + QT_TRANSLATE_NOOP("AddLabelDialog", "light"), + QT_TRANSLATE_NOOP("AddLabelDialog", "dark") }; } diff --git a/YACReaderLibrary/add_library_dialog.cpp b/YACReaderLibrary/add_library_dialog.cpp index 557cf5287..f63101ff7 100644 --- a/YACReaderLibrary/add_library_dialog.cpp +++ b/YACReaderLibrary/add_library_dialog.cpp @@ -13,12 +13,12 @@ AddLibraryDialog::AddLibraryDialog(QWidget *parent) void AddLibraryDialog::setupUI() { - textLabel = new QLabel(tr("Comics folder : ")); + textLabel = new QLabel(tr("Comics folder: ")); path = new QLineEdit; textLabel->setBuddy(path); connect(path, &QLineEdit::textChanged, this, &AddLibraryDialog::pathSetted); - nameLabel = new QLabel(tr("Library name : ")); + nameLabel = new QLabel(tr("Library name: ")); nameEdit = new QLineEdit; nameLabel->setBuddy(nameEdit); connect(nameEdit, &QLineEdit::textChanged, this, &AddLibraryDialog::nameSetted); diff --git a/YACReaderLibrary/cbl/CMakeLists.txt b/YACReaderLibrary/cbl/CMakeLists.txt new file mode 100644 index 000000000..6e17bb625 --- /dev/null +++ b/YACReaderLibrary/cbl/CMakeLists.txt @@ -0,0 +1,55 @@ +# CBL reading lists: reading, matching, and importing; no GUI, so the server can use it + +add_library(cbl STATIC + cbl_reader.h + cbl_reader.cpp + cbl_matcher.h + cbl_matcher.cpp + cbl_importer.h + cbl_importer.cpp + cbl_list_match.h + cbl_list_match.cpp + cbl_writer.h + cbl_writer.cpp +) +target_include_directories(cbl PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) +yacreader_apply_build_options(cbl) +target_link_libraries(cbl PUBLIC + Qt6::Core + Qt6::Sql + db_helper + library_common + QsLog +) + +# The import dialog of YACReaderLibrary +if(NOT BUILD_SERVER_STANDALONE) + add_library(cbl_gui STATIC + cbl_import_model.h + cbl_import_model.cpp + cbl_entry_detail.h + cbl_entry_detail.cpp + cbl_entries_view.h + cbl_entries_view.cpp + cbl_find_comic_page.h + cbl_find_comic_page.cpp + cbl_import_dialog.h + cbl_import_dialog.cpp + cbl_match_comic_dialog.h + cbl_match_comic_dialog.cpp + ) + target_include_directories(cbl_gui PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) + yacreader_apply_build_options(cbl_gui) + target_link_libraries(cbl_gui PUBLIC + Qt6::Core + Qt6::Widgets + Qt6::Sql + cbl + naturalsort + QsLog + ) +endif() diff --git a/YACReaderLibrary/cbl/cbl_entries_view.cpp b/YACReaderLibrary/cbl/cbl_entries_view.cpp new file mode 100644 index 000000000..e89656a2e --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_entries_view.cpp @@ -0,0 +1,145 @@ +#include "cbl_entries_view.h" + +#include "cbl_import_model.h" + +#include +#include +#include +#include + +#include + +namespace { + +class CblEntriesDelegate : public QStyledItemDelegate +{ +public: + explicit CblEntriesDelegate(CblEntriesView *view) + : QStyledItemDelegate(view), view(view) + { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + if (CblImportModel::isDetail(index)) + return; + + QStyledItemDelegate::paint(painter, option, index); + if (index.column() != CblImportModel::ActionColumn || !view->isHovered(index)) + return; + + QStyleOptionButton button; + button.initFrom(view); + button.rect = view->findButtonRect(option.rect); + button.text = view->findButtonText(); + button.state = QStyle::State_Enabled | QStyle::State_Raised; + view->style()->drawControl(QStyle::CE_PushButton, &button, painter, view); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + if (CblImportModel::isDetail(index)) { + const auto *widget = view->indexWidget(index.siblingAtColumn(0)); + return widget != nullptr ? QSize(0, widget->sizeHint().height()) : QSize(0, 0); + } + + auto size = QStyledItemDelegate::sizeHint(option, index); + // every row is as tall as the button, so the button fits on any row it shows on + if (index.column() == CblImportModel::ActionColumn) { + const auto button = view->findButtonRect(QRect(0, 0, 1000, 1000)); + size.setWidth(button.width() + 8); + size.setHeight(std::max(size.height(), button.height() + 2)); + } + return size; + } + +private: + CblEntriesView *view; +}; + +} + +CblEntriesView::CblEntriesView(QWidget *parent) + : QTreeView(parent) +{ + setMouseTracking(true); + setUniformRowHeights(false); + setItemDelegate(new CblEntriesDelegate(this)); +} + +bool CblEntriesView::isHovered(const QModelIndex &index) const +{ + return hovered.isValid() && !CblImportModel::isDetail(index) && index.row() == hovered.row() && index.parent() == hovered.parent(); +} + +QString CblEntriesView::findButtonText() const +{ + return tr("Find comic ›"); +} + +QRect CblEntriesView::findButtonRect(const QRect &cell) const +{ + QStyleOptionButton button; + button.initFrom(this); + button.text = findButtonText(); + const auto textSize = fontMetrics().size(Qt::TextShowMnemonic, button.text); + auto size = style()->sizeFromContents(QStyle::CT_PushButton, &button, textSize, this); + size.setHeight(std::min(size.height(), cell.height() - 2)); + QRect rect(QPoint(0, 0), size); + rect.moveCenter(cell.center()); + rect.moveRight(cell.right() - 4); + return rect; +} + +void CblEntriesView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const +{ + // the indentation of a detail would show the selection of its entry + if (CblImportModel::isDetail(index)) { + painter->fillRect(rect, palette().base()); + return; + } + if (isExpanded(index) || isHovered(index)) + QTreeView::drawBranches(painter, rect, index); +} + +void CblEntriesView::mouseMoveEvent(QMouseEvent *event) +{ + const auto index = indexAt(event->position().toPoint()); + setHovered(CblImportModel::isDetail(index) ? QModelIndex() : index); + QTreeView::mouseMoveEvent(event); +} + +void CblEntriesView::mousePressEvent(QMouseEvent *event) +{ + const auto position = event->position().toPoint(); + const auto index = indexAt(position); + if (event->button() == Qt::LeftButton && index.isValid() && !CblImportModel::isDetail(index) && index.column() == CblImportModel::ActionColumn && + findButtonRect(visualRect(index)).contains(position)) { + emit findComicRequested(index); + event->accept(); + return; + } + QTreeView::mousePressEvent(event); +} + +void CblEntriesView::leaveEvent(QEvent *event) +{ + setHovered(QModelIndex()); + QTreeView::leaveEvent(event); +} + +void CblEntriesView::setHovered(const QModelIndex &index) +{ + if (index.isValid() == hovered.isValid() && (!index.isValid() || (index.row() == hovered.row() && index.parent() == hovered.parent()))) + return; + + const auto previous = hovered; + hovered = index.isValid() ? QPersistentModelIndex(index.siblingAtColumn(0)) : QPersistentModelIndex(); + // the branch area is outside the cells, so the whole rows are repainted + for (const auto &row : { QModelIndex(previous), QModelIndex(hovered) }) { + if (row.isValid()) { + const auto rect = visualRect(row); + viewport()->update(QRect(0, rect.top(), viewport()->width(), rect.height())); + } + } +} diff --git a/YACReaderLibrary/cbl/cbl_entries_view.h b/YACReaderLibrary/cbl/cbl_entries_view.h new file mode 100644 index 000000000..d598720f9 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_entries_view.h @@ -0,0 +1,35 @@ +#ifndef CBL_ENTRIES_VIEW_H +#define CBL_ENTRIES_VIEW_H + +#include +#include + +// the entries of a CBL import: the expand arrow and the "Find comic" button show on the row under the mouse; a detail +// row shows its index widget across all columns +class CblEntriesView : public QTreeView +{ + Q_OBJECT + +public: + explicit CblEntriesView(QWidget *parent = nullptr); + + bool isHovered(const QModelIndex &index) const; + QRect findButtonRect(const QRect &cell) const; + QString findButtonText() const; + +signals: + void findComicRequested(const QModelIndex &index); + +protected: + void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + +private: + void setHovered(const QModelIndex &index); + + QPersistentModelIndex hovered; +}; + +#endif diff --git a/YACReaderLibrary/cbl/cbl_entry_detail.cpp b/YACReaderLibrary/cbl/cbl_entry_detail.cpp new file mode 100644 index 000000000..ef0a90869 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_entry_detail.cpp @@ -0,0 +1,362 @@ +#include "cbl_entry_detail.h" + +#include "qnaturalsorting.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +enum Field { + SeriesField, + NumberField, + VolumeField, + YearField, + FormatField, + ComicVineField, + FieldCount, +}; + +using Values = std::array; +using Differences = std::array; + +QString fieldName(int field) +{ + switch (field) { + case SeriesField: + return CblEntryDetail::tr("Series"); + case NumberField: + return CblEntryDetail::tr("Number"); + case VolumeField: + return CblEntryDetail::tr("Volume"); + case YearField: + return CblEntryDetail::tr("Year"); + case FormatField: + return CblEntryDetail::tr("Format"); + case ComicVineField: + return CblEntryDetail::tr("ComicVine ID"); + } + return { }; +} + +Values bookValues(const CblBook &book) +{ + const auto year = cblBookYear(book); + return { book.series, book.number, book.volume, year > 0 ? QString::number(year) : QString(), book.format.isEmpty() ? book.issueType : book.format, cblComicVineIssueId(book) }; +} + +Values metadataValues(const CblLibraryMetadata &metadata) +{ + const auto year = cblYearOfDate(metadata.date); + return { metadata.series, metadata.number, metadata.volume, year > 0 ? QString::number(year) : QString(), metadata.format, metadata.comicVineId }; +} + +bool differentText(const QString &a, const QString &b) +{ + return !a.trimmed().isEmpty() && !b.trimmed().isEmpty() && a.simplified().compare(b.simplified(), Qt::CaseInsensitive) != 0; +} + +// a value that is not known on one side is not different +Differences differences(const CblBook &book, const CblLibraryMetadata &metadata) +{ + const auto bookYear = cblBookYear(book); + const auto year = cblYearOfDate(metadata.date); + return { differentText(book.series, metadata.series), + !book.number.trimmed().isEmpty() && !metadata.number.trimmed().isEmpty() && !cblSameNumber(book.number, metadata.number), + differentText(book.volume, cblEffectiveVolume(metadata)), + bookYear > 0 && year > 0 && bookYear != year, + differentText(book.format.isEmpty() ? book.issueType : book.format, metadata.format), + differentText(cblComicVineIssueId(book), metadata.comicVineId) }; +} + +QString metadataTitle(const CblLibraryMetadata &metadata) +{ + return metadata.number.isEmpty() ? metadata.series : metadata.series + QStringLiteral(" #") + metadata.number; +} + +QString coverPath(const QString &libraryDataPath, const CblCandidate &candidate) +{ + if (const auto *comic = std::get_if(&candidate)) + return comic->hash.isEmpty() ? QString() : QDir(libraryDataPath).filePath(QStringLiteral("covers/") + comic->hash + QStringLiteral(".jpg")); + const auto &placeholder = std::get(candidate); + return placeholder.coverKey.isEmpty() ? QString() : QDir(libraryDataPath).filePath(QStringLiteral("covers/placeholders/") + placeholder.coverKey + QStringLiteral(".jpg")); +} + +bool hasFile(const CblCandidate &candidate) +{ + const auto *comic = std::get_if(&candidate); + return comic != nullptr && comic->hasFile; +} + +const CblLibraryMetadata &metadataOf(const CblCandidate &candidate) +{ + return std::visit([](const auto &item) -> const CblLibraryMetadata & { return item; }, candidate); +} + +// the distinct non-empty values, in natural order +QStringList distinctValues(QStringList values) +{ + values.removeAll(QString()); + values.removeDuplicates(); + std::sort(values.begin(), values.end(), naturalSortLessThanCI); + return values; +} + +// one column next to the book: a comic, a possible comic, or the closest series +struct Subject { + QString title; + QString subtitle; + std::optional candidate; + bool showsCover = false; + QString coverPath; + Values values; + Differences different { }; +}; + +QLabel *mutedLabel(const QString &text, QWidget *parent) +{ + auto label = new QLabel(text, parent); + label->setForegroundRole(QPalette::PlaceholderText); + return label; +} + +QLabel *boldLabel(const QString &text, QWidget *parent) +{ + auto label = new QLabel(text, parent); + auto font = label->font(); + font.setBold(true); + label->setFont(font); + return label; +} + +} + +QString cblMatchLevelText(CblMatchLevel level) +{ + switch (level) { + case CblMatchLevel::ComicVineId: + return CblEntryDetail::tr("ComicVine ID"); + case CblMatchLevel::SameSeries: + return CblEntryDetail::tr("series and number"); + case CblMatchLevel::SeriesWithoutVolume: + return CblEntryDetail::tr("series without the volume, and number"); + case CblMatchLevel::LettersAndDigits: + return CblEntryDetail::tr("letters and digits of the series, and number"); + case CblMatchLevel::SeriesWithYear: + return CblEntryDetail::tr("series with its year, and number"); + } + return { }; +} + +QColor cblWarningColor(const QPalette &palette) +{ + const bool dark = palette.color(QPalette::Base).lightness() < 128; + return dark ? QColor(0xE0, 0xA0, 0x30) : QColor(0xB2, 0x6B, 0x00); +} + +QString cblBookDetails(const CblBook &book) +{ + QStringList parts; + if (!book.volume.isEmpty()) + parts.append(CblEntryDetail::tr("volume %1").arg(book.volume)); + if (const auto year = cblBookYear(book); year > 0) + parts.append(QString::number(year)); + return parts.join(QStringLiteral(", ")); +} + +CblEntryDetail::CblEntryDetail(const CblImportEntry &entry, int row, int entryCount, const QList &closestSeries, const QString &libraryDataPath, const QColor &warningColor, QWidget *parent) + : QWidget(parent) +{ + setAutoFillBackground(true); + setBackgroundRole(QPalette::Base); + + QList subjects; + QString footer; + + const auto subjectOf = [&](const CblCandidate &candidate, const QString &title) { + Subject subject; + subject.title = title; + if (const auto *comic = std::get_if(&candidate); comic != nullptr && comic->hasFile) + subject.subtitle = CblImportModel::fileName(*comic); + else + subject.subtitle = metadataTitle(metadataOf(candidate)); + subject.candidate = candidate; + subject.showsCover = true; + subject.coverPath = coverPath(libraryDataPath, candidate); + subject.values = metadataValues(metadataOf(candidate)); + subject.different = differences(entry.book, metadataOf(candidate)); + return subject; + }; + + if (entry.choice) { + subjects.append(subjectOf(*entry.choice, hasFile(*entry.choice) ? tr("Comic in your library") : tr("Missing in your library"))); + subjects.last().candidate.reset(); + if (entry.changed) + footer = tr("Changed by you."); + else if (entry.current == entry.choice) + footer = tr("The entry has this comic."); + else if (hasFile(*entry.choice)) + footer = tr("Matched by %1.").arg(cblMatchLevelText(entry.match.level.value_or(CblMatchLevel::SameSeries))); + else + footer = tr("Your library already has this comic as missing. Matched by %1.").arg(cblMatchLevelText(entry.match.level.value_or(CblMatchLevel::SameSeries))); + } else if (!entry.match.candidates.isEmpty()) { + for (int i = 0; i < entry.match.candidates.size(); ++i) + subjects.append(subjectOf(entry.match.candidates.at(i), tr("Possible comic %1").arg(i + 1))); + if (entry.changed) + footer = tr("Changed by you: it is imported as missing."); + else if (entry.match.level == CblMatchLevel::ComicVineId) + footer = tr("The comics have the same ComicVine ID."); + else if (entry.match.candidates.size() == 1) + footer = tr("The comic has the same series and number, but its volume or year is different."); + else + footer = tr("The comics have the same series and number."); + footer += QLatin1Char(' ') + tr("Values that are different from the file are in amber."); + } else { + if (!closestSeries.isEmpty()) { + const auto &first = closestSeries.first(); + Subject subject; + subject.title = tr("Closest series in your library"); + subject.showsCover = true; + subject.coverPath = coverPath(libraryDataPath, first); + const auto folder = first.path.section('/', 0, -2).mid(1); + subject.subtitle = folder.isEmpty() ? tr("Library") : folder; + + QStringList numbers; + QStringList volumes; + QStringList years; + QStringList formats; + for (const auto &comic : closestSeries) { + numbers.append(comic.number); + volumes.append(cblEffectiveVolume(comic)); + const auto year = cblYearOfDate(comic.date); + years.append(year > 0 ? QString::number(year) : QString()); + formats.append(comic.format); + } + numbers = distinctValues(numbers); + volumes = distinctValues(volumes); + years = distinctValues(years); + formats = distinctValues(formats); + + constexpr int shownNumbers = 12; + auto numbersText = numbers.mid(0, shownNumbers).join(QStringLiteral(", ")); + if (numbers.size() > shownNumbers) + numbersText += QStringLiteral(", …"); + const auto contains = [](const QStringList &values, const QString &value, const std::function &same) { + return value.trimmed().isEmpty() || values.isEmpty() || std::any_of(values.cbegin(), values.cend(), [&](const QString &other) { return same(value, other); }); + }; + const auto sameText = [](const QString &a, const QString &b) { return !differentText(a, b); }; + const auto bookYear = cblBookYear(entry.book); + + subject.values = { first.series, numbers.isEmpty() ? QString() : tr("Has %1").arg(numbersText), volumes.join(QStringLiteral(", ")), years.join(QStringLiteral(", ")), formats.join(QStringLiteral(", ")), QString() }; + subject.different = { differentText(entry.book.series, first.series), + !contains(numbers, entry.book.number, cblSameNumber), + !contains(volumes, entry.book.volume, sameText), + !contains(years, bookYear > 0 ? QString::number(bookYear) : QString(), sameText), + !contains(formats, entry.book.format, sameText), + false }; + subjects.append(subject); + } + + QStringList tried; + if (!cblComicVineIssueId(entry.book).isEmpty()) + tried.append(cblMatchLevelText(CblMatchLevel::ComicVineId)); + if (!entry.book.series.trimmed().isEmpty()) { + for (const auto level : { CblMatchLevel::SameSeries, CblMatchLevel::SeriesWithoutVolume, CblMatchLevel::LettersAndDigits, CblMatchLevel::SeriesWithYear }) + tried.append(cblMatchLevelText(level)); + } + footer = entry.changed ? tr("Changed by you: it is imported as missing.") : tr("No comic found. Tried: %1.").arg(tried.join(QStringLiteral("; "))); + } + + const auto warningStyle = QStringLiteral("color: %1;").arg(warningColor.name()); + + grid = new QGridLayout; + grid->setHorizontalSpacing(24); + grid->setVerticalSpacing(4); + + auto bookHeader = new QVBoxLayout; + bookHeader->setSpacing(0); + bookHeader->addStretch(); + bookHeader->addWidget(boldLabel(tr("In the file"), this)); + bookHeader->addWidget(mutedLabel(tr("Entry %1 of %2").arg(row + 1).arg(entryCount), this)); + grid->addLayout(bookHeader, 0, 1, Qt::AlignBottom); + + for (int i = 0; i < subjects.size(); ++i) { + const auto &subject = subjects.at(i); + auto header = new QHBoxLayout; + header->setSpacing(8); + if (subject.showsCover) { + auto cover = new QLabel(this); + cover->setFixedSize(36, 54); + cover->setAlignment(Qt::AlignCenter); + QPixmap pixmap; + if (!subject.coverPath.isEmpty() && pixmap.load(subject.coverPath)) + cover->setPixmap(pixmap.scaled(cover->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + else + cover->setStyleSheet(QStringLiteral("background-color: palette(mid);")); + header->addWidget(cover); + } + + auto texts = new QVBoxLayout; + texts->setSpacing(0); + texts->addWidget(boldLabel(subject.title, this)); + texts->addWidget(mutedLabel(subject.subtitle, this)); + if (subject.candidate) { + const auto candidate = *subject.candidate; + auto button = new QPushButton(hasFile(candidate) ? tr("Use this comic") : tr("Use this missing comic"), this); + button->setAutoDefault(false); + connect(button, &QPushButton::clicked, this, [this, candidate] { emit candidateChosen(candidate); }); + texts->addWidget(button, 0, Qt::AlignLeft); + } + header->addLayout(texts); + header->addStretch(); + grid->addLayout(header, 0, i + 2, Qt::AlignBottom); + } + + const auto book = bookValues(entry.book); + for (int field = 0; field < FieldCount; ++field) { + const auto anyDifferent = std::any_of(subjects.cbegin(), subjects.cend(), [field](const Subject &subject) { return subject.different[field]; }); + auto name = new QLabel(anyDifferent ? fieldName(field) + QStringLiteral(" ≠") : fieldName(field), this); + if (anyDifferent) + name->setStyleSheet(warningStyle); + else + name->setForegroundRole(QPalette::PlaceholderText); + labelsWidth = std::max(labelsWidth, name->sizeHint().width()); + grid->addWidget(name, field + 1, 0); + grid->addWidget(new QLabel(book[field].isEmpty() ? QStringLiteral("—") : book[field], this), field + 1, 1); + + for (int i = 0; i < subjects.size(); ++i) { + const auto &value = subjects.at(i).values[field]; + const auto text = value.isEmpty() ? QStringLiteral("—") : value; + auto label = subjects.at(i).different[field] ? new QLabel(text, this) : mutedLabel(text, this); + if (subjects.at(i).different[field]) + label->setStyleSheet(warningStyle); + grid->addWidget(label, field + 1, i + 2); + } + } + grid->setColumnStretch(subjects.size() + 2, 1); + + auto footerLabel = mutedLabel(footer, this); + footerLabel->setWordWrap(true); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(12, 8, 12, 10); + layout->addLayout(grid); + layout->addSpacing(6); + layout->addWidget(footerLabel); +} + +void CblEntryDetail::alignLibraryColumns(int x) +{ + const auto left = layout()->contentsMargins().left(); + grid->setColumnMinimumWidth(0, labelsWidth); + grid->setColumnMinimumWidth(1, std::max(0, x - left - labelsWidth - 2 * grid->horizontalSpacing())); +} diff --git a/YACReaderLibrary/cbl/cbl_entry_detail.h b/YACReaderLibrary/cbl/cbl_entry_detail.h new file mode 100644 index 000000000..75f0cbea4 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_entry_detail.h @@ -0,0 +1,35 @@ +#ifndef CBL_ENTRY_DETAIL_H +#define CBL_ENTRY_DETAIL_H + +#include "cbl_import_model.h" + +#include + +class QGridLayout; + +// the book of an entry next to what it refers to: its choice, its possible comics, or the closest series +class CblEntryDetail : public QWidget +{ + Q_OBJECT + +public: + CblEntryDetail(const CblImportEntry &entry, int row, int entryCount, const QList &closestSeries, const QString &libraryDataPath, const QColor &warningColor, QWidget *parent = nullptr); + + // the first library column starts at `x`, so it can sit under a column of the table + void alignLibraryColumns(int x); + +signals: + void candidateChosen(const CblCandidate &candidate); + +private: + QGridLayout *grid; + int labelsWidth = 0; +}; + +// what the UI says about matching +QString cblMatchLevelText(CblMatchLevel level); +QString cblBookDetails(const CblBook &book); +// amber, for problems and values that are different +QColor cblWarningColor(const QPalette &palette); + +#endif diff --git a/YACReaderLibrary/cbl/cbl_find_comic_page.cpp b/YACReaderLibrary/cbl/cbl_find_comic_page.cpp new file mode 100644 index 000000000..5185f2bff --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_find_comic_page.cpp @@ -0,0 +1,418 @@ +#include "cbl_find_comic_page.h" + +#include "QsLog.h" +#include "cbl_entry_detail.h" +#include "data_base_management.h" +#include "db_helper.h" +#include "qnaturalsorting.h" +#include "search_query.h" +#include "yacreader_global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +enum ComicColumn { + FileColumn, + SeriesColumn, + NumberColumn, + VolumeColumn, + YearColumn, +}; + +constexpr int FolderIdRole = Qt::UserRole + 1; +constexpr int ComicPositionRole = Qt::UserRole + 2; + +CblLibraryComic libraryComic(const ComicDB &comic) +{ + CblLibraryComic result; + result.id = comic.info.id; + result.hasFile = true; + result.path = comic.file.path; + result.hash = comic.info.hash; + result.series = comic.info.series.toString(); + result.number = comic.info.number.toString(); + result.volume = comic.info.volume.toString(); + result.date = comic.info.date.toString(); + result.format = comic.info.format.toString(); + result.comicVineId = comic.info.comicVineID.toString(); + return result; +} + +bool different(const QString &a, const QString &b) +{ + return !a.trimmed().isEmpty() && !b.trimmed().isEmpty() && a.simplified().compare(b.simplified(), Qt::CaseInsensitive) != 0; +} + +} + +CblFindComicPage::CblFindComicPage(const QString &libraryPath, Mode mode, QWidget *parent) + : QWidget(parent), libraryPath(libraryPath), mode(mode) +{ + auto backButton = new QPushButton(tr("‹ All entries")); + connect(backButton, &QPushButton::clicked, this, &CblFindComicPage::backRequested); + + entryLabel = new QLabel; + entryLabel->setTextFormat(Qt::RichText); + problemLabel = new QLabel; + + auto entryTexts = new QVBoxLayout; + entryTexts->setSpacing(0); + entryTexts->addWidget(entryLabel); + entryTexts->addWidget(problemLabel); + + previousButton = new QPushButton(QStringLiteral("‹")); + nextButton = new QPushButton(QStringLiteral("›")); + for (auto *button : { previousButton, nextButton }) + button->setFixedWidth(button->sizeHint().height() * 3 / 2); + previousButton->setToolTip(tr("Previous problem")); + nextButton->setToolTip(tr("Next problem")); + problemCountLabel = new QLabel; + connect(previousButton, &QPushButton::clicked, this, &CblFindComicPage::previousProblemRequested); + connect(nextButton, &QPushButton::clicked, this, &CblFindComicPage::nextProblemRequested); + + auto header = new QHBoxLayout; + header->addWidget(backButton); + header->addSpacing(12); + header->addLayout(entryTexts, 1); + header->addWidget(previousButton); + header->addWidget(problemCountLabel); + header->addWidget(nextButton); + + foldersModel = new QStandardItemModel(this); + foldersModel->setHorizontalHeaderLabels({ tr("Folders") }); + foldersView = new QTreeView; + foldersView->setModel(foldersModel); + foldersView->setEditTriggers(QAbstractItemView::NoEditTriggers); + connect(foldersView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex ¤t) { + if (current.isValid()) { + searchEdit->clear(); + showFolder(current.data(FolderIdRole).toULongLong()); + } + }); + + breadcrumbLabel = new QLabel; + breadcrumbLabel->setTextFormat(Qt::RichText); + searchEdit = new QLineEdit; + searchEdit->setPlaceholderText(tr("Search the whole library")); + searchEdit->setClearButtonEnabled(true); + searchEdit->setMinimumWidth(260); + searchTimer = new QTimer(this); + searchTimer->setSingleShot(true); + searchTimer->setInterval(300); + connect(searchTimer, &QTimer::timeout, this, &CblFindComicPage::search); + connect(searchEdit, &QLineEdit::textEdited, searchTimer, qOverload<>(&QTimer::start)); + connect(searchEdit, &QLineEdit::textChanged, this, [this](const QString &text) { + if (!text.isEmpty()) + return; + if (onlyFolderId) + showFolder(*onlyFolderId); + else if (foldersView->currentIndex().isValid()) + showFolder(foldersView->currentIndex().data(FolderIdRole).toULongLong()); + }); + + auto comicsHeader = new QHBoxLayout; + comicsHeader->addWidget(breadcrumbLabel, 1); + comicsHeader->addWidget(searchEdit); + + comicsModel = new QStandardItemModel(this); + comicsModel->setHorizontalHeaderLabels({ tr("File"), tr("Series"), tr("No."), tr("Volume"), tr("Year") }); + comicsView = new QTreeView; + comicsView->setModel(comicsModel); + comicsView->setRootIsDecorated(false); + comicsView->setUniformRowHeights(true); + comicsView->setAlternatingRowColors(true); + comicsView->setEditTriggers(QAbstractItemView::NoEditTriggers); + comicsView->setSelectionBehavior(QAbstractItemView::SelectRows); + comicsView->header()->setSectionResizeMode(FileColumn, QHeaderView::Stretch); + comicsView->header()->setSectionResizeMode(SeriesColumn, QHeaderView::Stretch); + comicsView->header()->setStretchLastSection(false); + connect(comicsView, &QTreeView::doubleClicked, this, &CblFindComicPage::chooseSelectedComic); + connect(comicsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { + useButton->setEnabled(comicsView->selectionModel()->hasSelection()); + }); + + auto comicsPane = new QWidget; + auto comicsLayout = new QVBoxLayout(comicsPane); + comicsLayout->setContentsMargins(0, 0, 0, 0); + comicsLayout->addLayout(comicsHeader); + comicsLayout->addWidget(comicsView, 1); + + auto splitter = new QSplitter; + splitter->addWidget(foldersView); + splitter->addWidget(comicsPane); + splitter->setStretchFactor(1, 1); + splitter->setSizes({ 240, 700 }); + + nextProblemCheck = new QCheckBox(tr("Go to the next problem after I choose")); + nextProblemCheck->setChecked(true); + useButton = new QPushButton(tr("Use this comic")); + useButton->setAutoDefault(false); + useButton->setEnabled(false); + connect(useButton, &QPushButton::clicked, this, &CblFindComicPage::chooseSelectedComic); + + for (auto *button : { backButton, previousButton, nextButton }) + button->setAutoDefault(false); + + auto bottom = new QHBoxLayout; + bottom->addWidget(nextProblemCheck); + bottom->addStretch(); + if (mode == Mode::OneEntry) { + for (QWidget *widget : std::initializer_list { backButton, previousButton, problemCountLabel, nextButton, nextProblemCheck }) + widget->hide(); + auto cancelButton = new QPushButton(tr("Cancel")); + cancelButton->setAutoDefault(false); + connect(cancelButton, &QPushButton::clicked, this, &CblFindComicPage::backRequested); + bottom->addWidget(cancelButton); + } + bottom->addWidget(useButton); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(header); + layout->addWidget(splitter, 1); + layout->addLayout(bottom); +} + +std::optional> CblFindComicPage::loadFolders(QSqlDatabase &db) +{ + QSqlQuery query(db); + query.setForwardOnly(true); + if (!query.exec("SELECT id, parentId, name, path FROM folder WHERE id <> 1")) { + QLOG_ERROR() << "Unable to load the folders:" << query.lastError().text(); + return std::nullopt; + } + + QList folders; + while (query.next()) + folders.append({ query.value(0).toULongLong(), query.value(1).toULongLong(), query.value(2).toString(), query.value(3).toString() }); + std::sort(folders.begin(), folders.end(), [](const CblLibraryFolder &a, const CblLibraryFolder &b) { return naturalSortLessThanCI(a.name, b.name); }); + return folders; +} + +QString CblFindComicPage::startFolderPath(const CblImportEntry &entry, const CblMatcher &matcher) +{ + QString comicPath; + const auto pathOf = [](const CblCandidate &candidate) { + const auto *comic = std::get_if(&candidate); + return comic != nullptr ? comic->path : QString(); + }; + if (entry.choice) + comicPath = pathOf(*entry.choice); + for (int i = 0; comicPath.isEmpty() && i < entry.match.candidates.size(); ++i) + comicPath = pathOf(entry.match.candidates.at(i)); + if (comicPath.isEmpty()) { + const auto closest = matcher.closestSeries(entry.book); + for (int i = 0; comicPath.isEmpty() && i < closest.size(); ++i) + comicPath = closest.at(i).path; + } + return comicPath.section('/', 0, -2); +} + +void CblFindComicPage::setFolders(const QList &folders) +{ + foldersModel->removeRows(0, foldersModel->rowCount()); + folderItems.clear(); + folderIdsByPath.clear(); + + for (const auto &folder : folders) { + auto item = new QStandardItem(folder.name); + item->setData(folder.id, FolderIdRole); + folderItems.insert(folder.id, item); + folderIdsByPath.insert(folder.path, folder.id); + } + // the folders come sorted by name, so the children keep that order + for (const auto &folder : folders) { + auto *item = folderItems.value(folder.id); + if (auto *parentItem = folderItems.value(folder.parentId)) + parentItem->appendRow(item); + else + foldersModel->appendRow(item); + } +} + +void CblFindComicPage::limitToFolder(qulonglong folderId) +{ + onlyFolderId = folderId; + foldersView->hide(); + problemLabel->hide(); + searchEdit->setPlaceholderText(tr("Search in this folder")); +} + +void CblFindComicPage::setWarningColor(const QColor &color) +{ + warningColor = color; + problemLabel->setStyleSheet(QStringLiteral("color: %1;").arg(color.name())); +} + +void CblFindComicPage::showEntry(const CblImportEntry &shownEntry, int row, int entryCount, CblImportStatus status, const QString &folderPath, int problem, int problemCount, bool hasPrevious, bool hasNext) +{ + entry = shownEntry; + + const auto details = cblBookDetails(entry.book); + auto text = QStringLiteral("") + CblImportModel::bookTitle(entry.book).toHtmlEscaped() + QStringLiteral(""); + if (mode == Mode::ImportEntries) + text = tr("Entry %1 of %2").arg(row + 1).arg(entryCount) + QStringLiteral(" · ") + text; + if (!details.isEmpty()) + text += QStringLiteral(" (") + details.toHtmlEscaped() + QStringLiteral(")"); + entryLabel->setText(text); + + if (mode == Mode::OneEntry) + status = CblImportStatus::Missing; + switch (status) { + case CblImportStatus::Missing: + problemLabel->setText(tr("Missing: no comic found. Browse or search the library.")); + break; + case CblImportStatus::Ambiguous: + problemLabel->setText(tr("Ambiguous: %n possible comic(s). Browse or search the library.", "", entry.match.candidates.size())); + break; + case CblImportStatus::Matched: + case CblImportStatus::Changed: + problemLabel->setText(tr("Browse or search the library to choose another comic.")); + break; + } + + problemCountLabel->setText(problem >= 0 ? tr("Problem %1 of %2").arg(problem + 1).arg(problemCount) : tr("%n problem(s)", "", problemCount)); + previousButton->setEnabled(hasPrevious); + nextButton->setEnabled(hasNext); + + searchEdit->clear(); + if (onlyFolderId) { + showFolder(*onlyFolderId); + return; + } + const auto folderId = folderIdsByPath.value(folderPath); + if (auto *item = folderItems.value(folderId)) { + const auto index = item->index(); + foldersView->scrollTo(index); + if (foldersView->currentIndex() == index) + showFolder(folderId); + else + foldersView->setCurrentIndex(index); + } else { + foldersView->setCurrentIndex(QModelIndex()); + breadcrumbLabel->setText(tr("Library")); + showComics({ }); + } +} + +void CblFindComicPage::showFolder(qulonglong folderId) +{ + breadcrumbLabel->setText(breadcrumb(folderItems.value(folderId))); + + QList folderComics; + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + connectionName = db.connectionName(); + if (db.isOpen()) + folderComics = DBHelper::getComicsFromParent(folderId, db); + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + showComics(folderComics); +} + +void CblFindComicPage::search() +{ + const auto text = searchEdit->text().trimmed(); + if (text.isEmpty()) + return; + + QList found; + bool parsed = true; + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + connectionName = db.connectionName(); + if (db.isOpen()) { + try { + auto query = onlyFolderId ? comicsSearchQuery(db, text, *onlyFolderId) : comicsSearchQuery(db, text); + const auto indexes = ComicDB::columnIndexes(query.record(), "c", "ci"); + while (query.next()) + found.append(ComicDB::fromQuery(query, indexes)); + } catch (const std::exception &) { + parsed = false; + } + } + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + // an incomplete search text cannot be parsed; the previous results stay + if (!parsed) + return; + + breadcrumbLabel->setText(tr("Search results")); + showComics(found); +} + +void CblFindComicPage::showComics(const QList &shownComics) +{ + comics = shownComics; + comicsModel->removeRows(0, comicsModel->rowCount()); + useButton->setEnabled(false); + + const auto bookYear = cblBookYear(entry.book); + for (int i = 0; i < comics.size(); ++i) { + const auto comic = libraryComic(comics.at(i)); + const auto year = cblYearOfDate(comic.date); + const QList> values = { + { comics.at(i).file.fileName, false }, + { comic.series, different(entry.book.series, comic.series) }, + { comic.number, !entry.book.number.trimmed().isEmpty() && !comic.number.trimmed().isEmpty() && !cblSameNumber(entry.book.number, comic.number) }, + { comic.volume, different(entry.book.volume, cblEffectiveVolume(comic)) }, + { year > 0 ? QString::number(year) : QString(), bookYear > 0 && year > 0 && bookYear != year }, + }; + + QList items; + for (const auto &[value, isDifferent] : values) { + auto item = new QStandardItem(value); + item->setData(i, ComicPositionRole); + if (isDifferent && warningColor.isValid()) + item->setForeground(warningColor); + items.append(item); + } + comicsModel->appendRow(items); + } + comicsView->header()->resizeSections(QHeaderView::ResizeToContents); + comicsView->header()->setSectionResizeMode(FileColumn, QHeaderView::Stretch); + comicsView->header()->setSectionResizeMode(SeriesColumn, QHeaderView::Stretch); +} + +void CblFindComicPage::chooseSelectedComic() +{ + const auto selected = comicsView->selectionModel()->selectedRows(); + if (selected.isEmpty()) + return; + const auto position = selected.constFirst().data(ComicPositionRole).toInt(); + if (position < 0 || position >= comics.size()) + return; + emit comicChosen(libraryComic(comics.at(position)), nextProblemCheck->isChecked()); +} + +QString CblFindComicPage::breadcrumb(QStandardItem *folder) const +{ + if (folder == nullptr) + return tr("Library"); + + QStringList parts { QStringLiteral("") + folder->text().toHtmlEscaped() + QStringLiteral("") }; + for (auto *item = folder->parent(); item != nullptr; item = item->parent()) + parts.prepend(item->text().toHtmlEscaped()); + parts.prepend(tr("Library").toHtmlEscaped()); + return parts.join(QStringLiteral(" / ")); +} diff --git a/YACReaderLibrary/cbl/cbl_find_comic_page.h b/YACReaderLibrary/cbl/cbl_find_comic_page.h new file mode 100644 index 000000000..6e16cbb6f --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_find_comic_page.h @@ -0,0 +1,93 @@ +#ifndef CBL_FIND_COMIC_PAGE_H +#define CBL_FIND_COMIC_PAGE_H + +#include "cbl_import_model.h" +#include "comic_db.h" + +#include + +#include + +class CblMatcher; +class QCheckBox; +class QLabel; +class QLineEdit; +class QSqlDatabase; +class QPushButton; +class QStandardItem; +class QStandardItemModel; +class QTimer; +class QTreeView; + +struct CblLibraryFolder { + qulonglong id = 0; + qulonglong parentId = 0; + QString name; + QString path; +}; + +// browse the folders or search the library to choose the comic of one entry +class CblFindComicPage : public QWidget +{ + Q_OBJECT + +public: + // OneEntry has no entry list to go back to and no other problems; backRequested is its Cancel + enum class Mode { + ImportEntries, + OneEntry, + }; + + CblFindComicPage(const QString &libraryPath, Mode mode = Mode::ImportEntries, QWidget *parent = nullptr); + + // every folder but the root, sorted by name + static std::optional> loadFolders(QSqlDatabase &db); + // the folder of the comic the entry refers to, else of its first possible comic, else of the closest series + static QString startFolderPath(const CblImportEntry &entry, const CblMatcher &matcher); + + void setFolders(const QList &folders); + // only the comics of the folder are shown and searched, without the folder tree; after setFolders + void limitToFolder(qulonglong folderId); + void setWarningColor(const QColor &color); + // `problem` is the position of the entry among the problems, or -1 when it is not one + void showEntry(const CblImportEntry &entry, int row, int entryCount, CblImportStatus status, const QString &folderPath, int problem, int problemCount, bool hasPrevious, bool hasNext); + +signals: + void backRequested(); + void previousProblemRequested(); + void nextProblemRequested(); + void comicChosen(const CblLibraryComic &comic, bool goToNextProblem); + +private: + void showFolder(qulonglong folderId); + void search(); + void showComics(const QList &comics); + void chooseSelectedComic(); + QString breadcrumb(QStandardItem *folder) const; + + QString libraryPath; + Mode mode; + std::optional onlyFolderId; + CblImportEntry entry; + QColor warningColor; + QList comics; + + QLabel *entryLabel; + QLabel *problemLabel; + QLabel *problemCountLabel; + QPushButton *previousButton; + QPushButton *nextButton; + QTreeView *foldersView; + QStandardItemModel *foldersModel; + QHash folderItems; + QHash folderIdsByPath; + QLabel *breadcrumbLabel; + QLineEdit *searchEdit; + QTimer *searchTimer; + QTreeView *comicsView; + QStandardItemModel *comicsModel; + QCheckBox *nextProblemCheck; + QPushButton *useButton; +}; + +#endif diff --git a/YACReaderLibrary/cbl/cbl_import_dialog.cpp b/YACReaderLibrary/cbl/cbl_import_dialog.cpp new file mode 100644 index 000000000..3becd50a8 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_import_dialog.cpp @@ -0,0 +1,696 @@ +#include "cbl_import_dialog.h" + +#include "QsLog.h" +#include "cbl_entries_view.h" +#include "cbl_entry_detail.h" +#include "cbl_find_comic_page.h" +#include "cbl_importer.h" +#include "data_base_management.h" +#include "library_comic_match.h" +#include "yacreader_global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct CblImportFile { + QString filePath; + std::optional readError; + bool libraryError = false; + CblReadingList list; + QList entries; + QStringList readingListNames; + std::shared_ptr matcher; + QList folders; +}; + +namespace { + +// the other filter button ids are the statuses; QButtonGroup reads -1 as "choose an id", so it cannot mean all +constexpr int AllEntriesFilter = int(CblImportStatus::Changed) + 1; + +std::optional loadTopLevelReadingListNames(QSqlDatabase &db) +{ + QSqlQuery query(db); + query.setForwardOnly(true); + if (!query.exec("SELECT name FROM reading_list WHERE parentId IS NULL")) { + QLOG_ERROR() << "Unable to load the reading list names:" << query.lastError().text(); + return std::nullopt; + } + + QStringList names; + while (query.next()) + names.append(query.value(0).toString()); + return names; +} + +CblImportFile loadCblImportFile(const QString &filePath, const QString &libraryPath) +{ + CblImportFile result; + result.filePath = filePath; + + auto read = readCbl(filePath); + if (const auto *error = std::get_if(&read)) { + QLOG_WARN() << "Unable to read the reading list" << filePath << error->detail; + result.readError = *error; + return result; + } + result.list = std::get(std::move(read)); + + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + connectionName = db.connectionName(); + + const auto comics = db.isOpen() ? CblMatcher::loadLibraryComics(db) : std::nullopt; + const auto placeholders = db.isOpen() ? CblMatcher::loadLibraryPlaceholders(db) : std::nullopt; + const auto names = db.isOpen() ? loadTopLevelReadingListNames(db) : std::nullopt; + const auto folders = db.isOpen() ? CblFindComicPage::loadFolders(db) : std::nullopt; + if (!comics || !placeholders || !names || !folders) { + result.libraryError = true; + } else { + result.readingListNames = *names; + result.folders = *folders; + result.matcher = std::make_shared(*comics, *placeholders); + result.entries.reserve(result.list.books.size()); + for (const auto &book : std::as_const(result.list.books)) { + CblImportEntry entry; + entry.book = book; + entry.match = result.matcher->match(book); + entry.choice = entry.match.accepted; + result.entries.append(entry); + } + } + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + return result; +} + +// the comics of a list entry are comics with a file: an entry that has one keeps it as its match +CblImportFile loadCblListEntries(const QList &listEntries, const QString &libraryPath) +{ + CblImportFile result; + + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + connectionName = db.connectionName(); + + const auto comics = db.isOpen() ? CblMatcher::loadLibraryComics(db) : std::nullopt; + const auto folders = db.isOpen() ? CblFindComicPage::loadFolders(db) : std::nullopt; + if (!comics || !folders) { + result.libraryError = true; + } else { + result.folders = *folders; + QList withFile; + std::copy_if(comics->cbegin(), comics->cend(), std::back_inserter(withFile), [](const CblLibraryComic &comic) { return comic.hasFile; }); + result.matcher = std::make_shared(withFile); + result.entries.reserve(listEntries.size()); + for (const auto &listEntry : listEntries) { + CblImportEntry entry; + entry.book = listEntry.book; + entry.current = listEntry.current; + const auto *comic = std::get_if(&listEntry.current); + if (comic != nullptr && comic->hasFile) { + entry.match.accepted = listEntry.current; + entry.match.candidates = { listEntry.current }; + } else { + entry.match = result.matcher->match(listEntry.book); + } + entry.choice = entry.match.accepted; + result.entries.append(entry); + } + } + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + return result; +} + +// checkable buttons that look like the segments of a filter; colours come from the palette, which the theme drives +QString filterButtonsStyleSheet() +{ + return QStringLiteral( + "QPushButton {" + " border: 1px solid palette(mid);" + " border-radius: 4px;" + " padding: 4px 14px;" + " background-color: palette(button);" + " color: palette(button-text);" + "}" + "QPushButton:hover { background-color: palette(midlight); }" + "QPushButton:checked {" + " background-color: palette(highlight);" + " color: palette(highlighted-text);" + " border-color: palette(highlight);" + "}"); +} + +} + +CblImportDialog::CblImportDialog(const QStringList &filePaths, const QString &libraryPath, QWidget *parent) + : CblImportDialog(libraryPath, parent) +{ + this->filePaths = filePaths; + loadCurrentFile(); +} + +CblImportDialog::CblImportDialog(const QString &listName, const QList &entries, const QString &libraryPath, QWidget *parent) + : CblImportDialog(libraryPath, parent) +{ + listEntries = entries; + this->listName = listName; + setWindowTitle(tr("Match to library comics — %1").arg(listName)); + nameRow->hide(); + nameConflictLabel->hide(); + skipButton->hide(); + importButton->setText(tr("Apply")); + loadListEntries(); +} + +CblImportDialog::CblImportDialog(const QString &libraryPath, QWidget *parent) + : QDialog(parent), libraryPath(libraryPath) +{ + model = new CblImportModel(this); + filterModel = new CblImportFilterModel(this); + filterModel->setSourceModel(model); + + pages = new QStackedWidget; + messagePage = createMessagePage(); + entriesPage = createEntriesPage(); + findPage = new CblFindComicPage(libraryPath); + pages->addWidget(messagePage); + pages->addWidget(entriesPage); + pages->addWidget(findPage); + connect(findPage, &CblFindComicPage::backRequested, this, &CblImportDialog::showEntries); + connect(findPage, &CblFindComicPage::previousProblemRequested, this, [this] { showProblem(false); }); + connect(findPage, &CblFindComicPage::nextProblemRequested, this, [this] { showProblem(true); }); + connect(findPage, &CblFindComicPage::comicChosen, this, [this](const CblLibraryComic &comic, bool goToNextProblem) { + model->choose(findRow, comic); + if (!goToNextProblem || !showProblem(true)) + showEntries(); + }); + + summaryLabel = new QLabel; + + closeButton = new QPushButton(tr("Close")); + skipButton = new QPushButton(tr("Skip")); + importButton = new QPushButton(tr("Import")); + // Return in the name or filter field must not import + for (auto *button : { closeButton, skipButton, importButton }) + button->setAutoDefault(false); + connect(closeButton, &QPushButton::clicked, this, &QDialog::reject); + connect(skipButton, &QPushButton::clicked, this, &CblImportDialog::nextFile); + connect(importButton, &QPushButton::clicked, this, [this] { + if (listEntries) + applyListChanges(); + else + importCurrentFile(); + }); + + bottomBar = new QWidget; + auto buttons = new QHBoxLayout(bottomBar); + buttons->setContentsMargins(0, 0, 0, 0); + buttons->addWidget(summaryLabel); + buttons->addStretch(); + buttons->addWidget(closeButton); + buttons->addWidget(skipButton); + buttons->addWidget(importButton); + + auto layout = new QVBoxLayout(this); + layout->addWidget(pages, 1); + layout->addWidget(bottomBar); + + resize(1000, 700); +} + +CblImportDialog::~CblImportDialog() = default; + +QWidget *CblImportDialog::createMessagePage() +{ + auto page = new QWidget; + auto layout = new QVBoxLayout(page); + + messageLabel = new QLabel; + messageLabel->setAlignment(Qt::AlignCenter); + messageLabel->setWordWrap(true); + layout->addWidget(messageLabel); + + return page; +} + +QWidget *CblImportDialog::createEntriesPage() +{ + auto page = new QWidget; + auto layout = new QVBoxLayout(page); + layout->setContentsMargins(0, 0, 0, 0); + + nameEdit = new QLineEdit; + connect(nameEdit, &QLineEdit::textChanged, this, &CblImportDialog::updateNameConflict); + auto nameLabel = new QLabel(tr("&Name:")); + nameLabel->setBuddy(nameEdit); + nameConflictLabel = new QLabel(tr("A reading list with this name already exists. Import creates another one.")); + + nameRow = new QWidget; + auto nameLayout = new QHBoxLayout(nameRow); + nameLayout->setContentsMargins(0, 0, 0, 0); + nameLayout->addWidget(nameLabel); + nameLayout->addWidget(nameEdit, 1); + + filterButtons = new QButtonGroup(this); + auto filterButtonsWidget = new QWidget; + auto filterButtonsLayout = new QHBoxLayout(filterButtonsWidget); + filterButtonsLayout->setContentsMargins(0, 0, 0, 0); + for (const auto id : { AllEntriesFilter, int(CblImportStatus::Matched), int(CblImportStatus::Ambiguous), int(CblImportStatus::Missing), int(CblImportStatus::Changed) }) { + auto button = new QPushButton; + button->setCheckable(true); + button->setAutoDefault(false); + filterButtons->addButton(button, id); + filterButtonList.append(button); + filterButtonsLayout->addWidget(button); + } + filterButtons->button(AllEntriesFilter)->setChecked(true); + filterButtonsWidget->setStyleSheet(filterButtonsStyleSheet()); + connect(filterButtons, &QButtonGroup::idClicked, this, [this](int id) { + filterModel->setStatusFilter(id == AllEntriesFilter ? std::nullopt : std::optional(CblImportStatus(id))); + }); + + filterEdit = new QLineEdit; + filterEdit->setPlaceholderText(tr("Filter entries")); + filterEdit->setClearButtonEnabled(true); + connect(filterEdit, &QLineEdit::textChanged, filterModel, &QSortFilterProxyModel::setFilterFixedString); + + auto filterRow = new QHBoxLayout; + filterRow->addWidget(filterButtonsWidget); + filterRow->addStretch(); + filterRow->addWidget(filterEdit); + + entriesView = new CblEntriesView; + entriesView->setModel(filterModel); + entriesView->setAlternatingRowColors(true); + entriesView->setSelectionMode(QAbstractItemView::ExtendedSelection); + entriesView->setSelectionBehavior(QAbstractItemView::SelectRows); + entriesView->setEditTriggers(QAbstractItemView::NoEditTriggers); + entriesView->header()->setStretchLastSection(false); + entriesView->header()->setSectionResizeMode(CblImportModel::NumberColumn, QHeaderView::ResizeToContents); + entriesView->header()->setSectionResizeMode(CblImportModel::BookColumn, QHeaderView::Stretch); + entriesView->header()->setSectionResizeMode(CblImportModel::ComicColumn, QHeaderView::Stretch); + entriesView->header()->setSectionResizeMode(CblImportModel::StatusColumn, QHeaderView::ResizeToContents); + entriesView->header()->setSectionResizeMode(CblImportModel::ActionColumn, QHeaderView::ResizeToContents); + connect(entriesView, &QTreeView::expanded, this, &CblImportDialog::createDetail); + connect(entriesView->header(), &QHeaderView::sectionResized, this, [this] { + for (auto *detail : entriesView->viewport()->findChildren()) + alignDetail(detail); + }); + connect(entriesView, &CblEntriesView::findComicRequested, this, [this](const QModelIndex &index) { + showFind(filterModel->mapToSource(index).row()); + }); + entriesView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(entriesView, &QTreeView::customContextMenuRequested, this, &CblImportDialog::showEntriesMenu); + connect(model, &QAbstractItemModel::dataChanged, this, &CblImportDialog::updateCounts); + // a detail can change its own entry, so it is replaced after its signal returns + connect(model, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight) { + QList changed; + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + const auto index = filterModel->mapFromSource(model->index(row, 0)); + if (index.isValid() && entriesView->isExpanded(index)) + changed.append(index); + } + if (!changed.isEmpty()) + QMetaObject::invokeMethod(this, [this, changed] { + for (const auto &index : changed) { + if (index.isValid() && entriesView->isExpanded(index)) + createDetail(index); + } }, Qt::QueuedConnection); + }); + + layout->addWidget(nameRow); + layout->addWidget(nameConflictLabel); + layout->addLayout(filterRow); + layout->addWidget(entriesView, 1); + + return page; +} + +void CblImportDialog::loadCurrentFile() +{ + file.reset(); + findRow = -1; + updateTitle(); + showMessage(tr("Matching the books of %1 with your library...").arg(QFileInfo(filePaths.at(currentFile)).fileName())); + skipButton->setEnabled(false); + + auto result = std::make_shared(); + auto thread = QThread::create([result, filePath = filePaths.at(currentFile), libraryPath = libraryPath] { + *result = loadCblImportFile(filePath, libraryPath); + }); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, this, [this, result] { showFile(*result); }); + thread->start(); +} + +void CblImportDialog::loadListEntries() +{ + showMessage(tr("Matching the entries of %1 with your library...").arg(listName)); + + auto result = std::make_shared(); + auto thread = QThread::create([result, entries = *listEntries, libraryPath = libraryPath] { + *result = loadCblListEntries(entries, libraryPath); + }); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, this, [this, result] { showFile(*result); }); + thread->start(); +} + +void CblImportDialog::applyListChanges() +{ + if (!file || !listEntries) + return; + + QList changes; + QList matched; + QSet listIds; + auto listKind = ListKind::ReadingList; + for (int row = 0; row < model->rowCount(); ++row) { + const auto &entry = model->entries().at(row); + const auto &listEntry = listEntries->at(row); + listIds.insert(listEntry.listId); + listKind = listEntry.id.listKind; + + const auto *chosen = entry.choice ? std::get_if(&*entry.choice) : nullptr; + const auto *currentComic = std::get_if(&listEntry.current); + if (chosen != nullptr && chosen->hasFile) { + if (currentComic != nullptr && currentComic->id == chosen->id) + continue; + changes.append({ listEntry.id, chosen->id }); + if (const auto *placeholder = std::get_if(&listEntry.current)) + matched.append(placeholder->id); + else if (!currentComic->hasFile) + matched.append(currentComic->id); + } else if (!entry.choice && currentComic != nullptr && currentComic->hasFile) { + changes.append({ listEntry.id, std::nullopt }); + } + } + if (changes.isEmpty()) { + accept(); + return; + } + + const auto dataPath = YACReader::LibraryPaths::libraryDataPath(libraryPath); + std::optional otherLists; + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(dataPath); + connectionName = db.connectionName(); + if (db.isOpen()) + otherLists = otherListsWithEntriesOf(matched, listKind, listIds, db); + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + + if (otherLists && !otherLists->isEmpty()) { + const auto answer = QMessageBox::question(this, + tr("Match to library comics"), + tr("This also gives comics to entries in %n other list(s): %1", "", otherLists->size()).arg(otherLists->join(QStringLiteral(", "))), + QMessageBox::Apply | QMessageBox::Cancel, + QMessageBox::Apply); + if (answer != QMessageBox::Apply) + return; + } + + bool applied = false; + if (otherLists) { + ComicMatchFiles files; + { + auto db = DataBaseManagement::loadDatabase(dataPath); + connectionName = db.connectionName(); + db.transaction(); + applied = applyCblListChanges(changes, dataPath, db, files) && db.commit(); + if (!applied) + db.rollback(); + } + QSqlDatabase::removeDatabase(connectionName); + finishComicMatchFiles(dataPath, files, applied); + } + + if (!applied) { + QMessageBox::critical(this, tr("Match to library comics"), tr("The changes could not be applied. Nothing was changed.")); + return; + } + emit listEntriesChanged(); + accept(); +} + +void CblImportDialog::showFile(const CblImportFile &loaded) +{ + skipButton->setEnabled(true); + + if (loaded.readError) { + switch (loaded.readError->kind) { + case CblReadError::CannotOpen: + showMessage(tr("The file cannot be opened.")); + break; + case CblReadError::Malformed: + showMessage(tr("The file is damaged (line %1, column %2).").arg(loaded.readError->line).arg(loaded.readError->column)); + break; + case CblReadError::NotAReadingList: + showMessage(tr("The file is not a reading list.")); + break; + } + return; + } + if (loaded.libraryError) { + showMessage(tr("The library cannot be read.")); + return; + } + + file = std::make_unique(loaded); + model->setWarningColor(warningColor()); + findPage->setWarningColor(warningColor()); + nameConflictLabel->setStyleSheet(QStringLiteral("color: %1;").arg(warningColor().name())); + findPage->setFolders(file->folders); + model->setEntries(file->entries); + filterEdit->clear(); + filterButtons->button(AllEntriesFilter)->click(); + updateCounts(); + if (!listEntries) { + nameEdit->setText(cblListName(file->list, file->filePath)); + updateNameConflict(); + } + + showEntries(); + importButton->setEnabled(true); + summaryLabel->setVisible(true); +} + +void CblImportDialog::showEntries() +{ + pages->setCurrentWidget(entriesPage); + bottomBar->setVisible(true); + + if (findRow >= 0 && findRow < model->rowCount()) { + const auto index = filterModel->mapFromSource(model->index(findRow, 0)); + if (index.isValid()) { + entriesView->scrollTo(index); + entriesView->setCurrentIndex(index); + } + } +} + +void CblImportDialog::showFind(int row) +{ + if (!file || row < 0 || row >= model->rowCount()) + return; + + findRow = row; + const auto &entry = model->entries().at(row); + const auto problems = model->problemRows(); + const auto problem = problems.indexOf(row); + const bool hasPrevious = !problems.isEmpty() && problems.constFirst() < row; + const bool hasNext = !problems.isEmpty() && problems.constLast() > row; + + findPage->showEntry(entry, row, model->rowCount(), model->status(row), CblFindComicPage::startFolderPath(entry, *file->matcher), problem, problems.size(), hasPrevious, hasNext); + pages->setCurrentWidget(findPage); + bottomBar->setVisible(false); +} + +bool CblImportDialog::showProblem(bool forward) +{ + const auto problems = model->problemRows(); + if (forward) { + const auto next = std::find_if(problems.cbegin(), problems.cend(), [this](int row) { return row > findRow; }); + if (next == problems.cend()) + return false; + showFind(*next); + } else { + const auto previous = std::find_if(problems.crbegin(), problems.crend(), [this](int row) { return row < findRow; }); + if (previous == problems.crend()) + return false; + showFind(*previous); + } + return true; +} + +void CblImportDialog::alignDetail(CblEntryDetail *detail) const +{ + // a detail is a child row, so its widget starts two indentations in: the root decoration and its own level + const auto *header = entriesView->header(); + const auto detailLeft = header->sectionViewportPosition(CblImportModel::NumberColumn) + 2 * entriesView->indentation(); + detail->alignLibraryColumns(header->sectionViewportPosition(CblImportModel::ComicColumn) - detailLeft); +} + +void CblImportDialog::createDetail(const QModelIndex &index) +{ + if (!file || !index.isValid() || CblImportModel::isDetail(index)) + return; + + const auto row = filterModel->mapToSource(index).row(); + const auto &entry = model->entries().at(row); + const auto closest = entry.choice || !entry.match.candidates.isEmpty() ? QList() : file->matcher->closestSeries(entry.book); + auto detail = new CblEntryDetail(entry, row, model->rowCount(), closest, YACReader::LibraryPaths::libraryDataPath(libraryPath), warningColor()); + connect(detail, &CblEntryDetail::candidateChosen, this, [this, row](const CblCandidate &candidate) { model->choose(row, candidate); }); + + alignDetail(detail); + entriesView->setFirstColumnSpanned(0, index, true); + entriesView->setIndexWidget(filterModel->index(0, 0, index), detail); + entriesView->doItemsLayout(); +} + +void CblImportDialog::showMessage(const QString &message) +{ + messageLabel->setText(message); + pages->setCurrentWidget(messagePage); + bottomBar->setVisible(true); + importButton->setEnabled(false); + summaryLabel->setVisible(false); +} + +void CblImportDialog::importCurrentFile() +{ + if (!file) + return; + + auto name = nameEdit->text().trimmed(); + if (name.isEmpty()) + name = cblListName(file->list, file->filePath); + + std::optional id; + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + connectionName = db.connectionName(); + if (db.isOpen()) + id = importCblReadingList(name, file->list, model->choices(), db); + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + + if (!id) { + QMessageBox::critical(this, tr("Import reading lists"), tr("The reading list could not be imported. Nothing was written.")); + return; + } + + emit readingListImported(*id, name); + nextFile(); +} + +void CblImportDialog::nextFile() +{ + if (currentFile + 1 >= filePaths.size()) { + accept(); + return; + } + + ++currentFile; + loadCurrentFile(); +} + +void CblImportDialog::updateTitle() +{ + const auto fileName = QFileInfo(filePaths.at(currentFile)).fileName(); + if (filePaths.size() > 1) + setWindowTitle(tr("Import reading lists — %1 (%2 of %3)").arg(fileName).arg(currentFile + 1).arg(filePaths.size())); + else + setWindowTitle(tr("Import reading lists — %1").arg(fileName)); + + skipButton->setVisible(currentFile + 1 < filePaths.size()); +} + +void CblImportDialog::updateCounts() +{ + const auto total = model->rowCount(); + const QStringList texts = { + tr("All (%1)").arg(total), + tr("Matched (%1)").arg(model->count(CblImportStatus::Matched)), + tr("Ambiguous (%1)").arg(model->count(CblImportStatus::Ambiguous)), + tr("Missing (%1)").arg(model->count(CblImportStatus::Missing)), + tr("Changed by you (%1)").arg(model->count(CblImportStatus::Changed)), + }; + for (int i = 0; i < filterButtonList.size(); ++i) + filterButtonList.at(i)->setText(texts.at(i)); + + summaryLabel->setText(tr("%1 of %n have a comic", "", total).arg(model->withComicCount())); +} + +void CblImportDialog::updateNameConflict() +{ + const bool exists = file && file->readingListNames.contains(nameEdit->text().trimmed(), Qt::CaseInsensitive); + nameConflictLabel->setVisible(exists); +} + +void CblImportDialog::showEntriesMenu(const QPoint &position) +{ + const auto rows = selectedRows(); + if (rows.isEmpty()) + return; + + const bool anyChanged = std::any_of(rows.cbegin(), rows.cend(), [this](int row) { return model->status(row) == CblImportStatus::Changed; }); + const bool anyCanBeMissing = std::any_of(rows.cbegin(), rows.cend(), [this](int row) { + return model->entries().at(row).choice || model->status(row) == CblImportStatus::Ambiguous; + }); + + QMenu menu(this); + if (rows.size() == 1) { + auto find = menu.addAction(tr("Find comic...")); + connect(find, &QAction::triggered, this, [this, row = rows.constFirst()] { showFind(row); }); + menu.addSeparator(); + } + auto original = menu.addAction(tr("Use the original match")); + original->setEnabled(anyChanged); + connect(original, &QAction::triggered, this, [this, rows] { model->useOriginalMatch(rows); }); + auto missing = menu.addAction(listEntries ? tr("Make missing") : tr("Import as missing")); + missing->setEnabled(anyCanBeMissing); + connect(missing, &QAction::triggered, this, [this, rows] { model->importAsMissing(rows); }); + menu.exec(entriesView->viewport()->mapToGlobal(position)); +} + +QList CblImportDialog::selectedRows() const +{ + QList rows; + for (const auto &index : entriesView->selectionModel()->selectedRows()) + rows.append(filterModel->mapToSource(index).row()); + return rows; +} + +QColor CblImportDialog::warningColor() const +{ + return cblWarningColor(palette()); +} diff --git a/YACReaderLibrary/cbl/cbl_import_dialog.h b/YACReaderLibrary/cbl/cbl_import_dialog.h new file mode 100644 index 000000000..b219ab6ef --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_import_dialog.h @@ -0,0 +1,93 @@ +#ifndef CBL_IMPORT_DIALOG_H +#define CBL_IMPORT_DIALOG_H + +#include "cbl_import_model.h" +#include "cbl_list_match.h" +#include "library_ids.h" + +#include + +#include + +class CblEntriesView; +class CblEntryDetail; +class CblFindComicPage; +class QButtonGroup; +class QLabel; +class QLineEdit; +class QPushButton; +class QStackedWidget; + +struct CblImportFile; + +// imports the files one after the other; each Import writes the reading list of the current file. In a list, it +// matches the entries of a reading list or label (or some of them) to library comics, and Apply writes the changes. +class CblImportDialog : public QDialog +{ + Q_OBJECT + +public: + CblImportDialog(const QStringList &filePaths, const QString &libraryPath, QWidget *parent = nullptr); + CblImportDialog(const QString &listName, const QList &entries, const QString &libraryPath, QWidget *parent = nullptr); + ~CblImportDialog() override; + +signals: + void readingListImported(ReadingListId id, const QString &name); + void listEntriesChanged(); + +private: + explicit CblImportDialog(const QString &libraryPath, QWidget *parent); + void loadListEntries(); + void applyListChanges(); + QWidget *createMessagePage(); + QWidget *createEntriesPage(); + void loadCurrentFile(); + void showFile(const CblImportFile &file); + void showMessage(const QString &message); + void showEntries(); + void showFind(int row); + // the next or previous Ambiguous or Missing entry; false when there is none + bool showProblem(bool forward); + void createDetail(const QModelIndex &index); + void alignDetail(CblEntryDetail *detail) const; + void importCurrentFile(); + void nextFile(); + void updateTitle(); + void updateCounts(); + void updateNameConflict(); + void showEntriesMenu(const QPoint &position); + QList selectedRows() const; + QColor warningColor() const; + + QStringList filePaths; + // set in a list + std::optional> listEntries; + QString listName; + QString libraryPath; + int currentFile = 0; + // the entry of the Find comic page + int findRow = -1; + std::unique_ptr file; + + QStackedWidget *pages; + QWidget *messagePage; + QLabel *messageLabel; + QWidget *entriesPage; + QWidget *nameRow; + QLineEdit *nameEdit; + QLabel *nameConflictLabel; + QButtonGroup *filterButtons; + QList filterButtonList; + QLineEdit *filterEdit; + CblEntriesView *entriesView; + CblFindComicPage *findPage; + QWidget *bottomBar; + CblImportModel *model; + CblImportFilterModel *filterModel; + QLabel *summaryLabel; + QPushButton *closeButton; + QPushButton *skipButton; + QPushButton *importButton; +}; + +#endif diff --git a/YACReaderLibrary/cbl/cbl_import_model.cpp b/YACReaderLibrary/cbl/cbl_import_model.cpp new file mode 100644 index 000000000..bddb4a3b0 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_import_model.cpp @@ -0,0 +1,284 @@ +#include "cbl_import_model.h" + +#include + +#include + +// the internal id of an entry is 0; the one of a detail is the row of its entry plus one +static bool isDetailIndex(const QModelIndex &index) +{ + return index.isValid() && index.internalId() != 0; +} + +CblImportModel::CblImportModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +QModelIndex CblImportModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column < 0 || column >= ColumnCount) + return { }; + if (!parent.isValid()) + return row >= 0 && row < importEntries.size() ? createIndex(row, column, quintptr(0)) : QModelIndex(); + if (isDetailIndex(parent) || row != 0) + return { }; + return createIndex(0, column, quintptr(parent.row() + 1)); +} + +QModelIndex CblImportModel::parent(const QModelIndex &child) const +{ + if (!isDetailIndex(child)) + return { }; + return createIndex(int(child.internalId() - 1), 0, quintptr(0)); +} + +int CblImportModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return importEntries.size(); + return !isDetailIndex(parent) && parent.column() == 0 ? 1 : 0; +} + +int CblImportModel::columnCount(const QModelIndex &) const +{ + return ColumnCount; +} + +Qt::ItemFlags CblImportModel::flags(const QModelIndex &index) const +{ + if (isDetailIndex(index)) + return Qt::ItemIsEnabled; + return QAbstractItemModel::flags(index); +} + +// also for the indexes of a proxy model +bool CblImportModel::isDetail(const QModelIndex &index) +{ + return index.isValid() && index.parent().isValid(); +} + +QVariant CblImportModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || isDetailIndex(index) || index.row() >= importEntries.size()) + return { }; + + const auto &entry = importEntries.at(index.row()); + const auto entryStatus = status(index.row()); + + if (role == StatusRole) + return int(entryStatus); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case NumberColumn: + return index.row() + 1; + case BookColumn: + return bookTitle(entry.book); + case ComicColumn: + return comicText(entry); + case StatusColumn: + switch (entryStatus) { + case CblImportStatus::Matched: + return tr("Matched"); + case CblImportStatus::Ambiguous: + return tr("Ambiguous"); + case CblImportStatus::Missing: + return tr("Missing"); + case CblImportStatus::Changed: + return tr("Changed"); + } + } + return { }; + } + + if (role == Qt::FontRole && index.column() == ComicColumn && comicTextIsNote(entry)) { + QFont font; + font.setItalic(true); + return font; + } + + if (role == Qt::ForegroundRole && index.column() == StatusColumn && warningColor.isValid() && + (entryStatus == CblImportStatus::Ambiguous || entryStatus == CblImportStatus::Missing)) + return warningColor; + + return { }; +} + +QVariant CblImportModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return { }; + + switch (section) { + case NumberColumn: + return tr("#"); + case BookColumn: + return tr("In the file"); + case ComicColumn: + return tr("Comic in your library"); + case StatusColumn: + return tr("Status"); + case ActionColumn: + return QString(); + } + return { }; +} + +void CblImportModel::setEntries(const QList &entries) +{ + beginResetModel(); + importEntries = entries; + endResetModel(); +} + +void CblImportModel::setWarningColor(const QColor &color) +{ + warningColor = color; + if (!importEntries.isEmpty()) + emit dataChanged(index(0, StatusColumn), index(int(importEntries.size()) - 1, StatusColumn), { Qt::ForegroundRole }); +} + +CblImportStatus CblImportModel::status(int row) const +{ + const auto &entry = importEntries.at(row); + if (entry.changed) + return CblImportStatus::Changed; + if (entry.choice) + return CblImportStatus::Matched; + return entry.match.candidates.isEmpty() ? CblImportStatus::Missing : CblImportStatus::Ambiguous; +} + +int CblImportModel::count(CblImportStatus status) const +{ + int result = 0; + for (int row = 0; row < importEntries.size(); ++row) { + if (this->status(row) == status) + ++result; + } + return result; +} + +int CblImportModel::withComicCount() const +{ + return std::count_if(importEntries.cbegin(), importEntries.cend(), [](const CblImportEntry &entry) { + const auto *comic = entry.choice ? std::get_if(&*entry.choice) : nullptr; + return comic != nullptr && comic->hasFile; + }); +} + +QList CblImportModel::problemRows() const +{ + QList rows; + for (int row = 0; row < importEntries.size(); ++row) { + const auto rowStatus = status(row); + if (rowStatus == CblImportStatus::Ambiguous || rowStatus == CblImportStatus::Missing) + rows.append(row); + } + return rows; +} + +QList> CblImportModel::choices() const +{ + QList> result; + result.reserve(importEntries.size()); + for (const auto &entry : importEntries) + result.append(entry.choice); + return result; +} + +void CblImportModel::choose(int row, const CblCandidate &candidate) +{ + changeRows({ row }, [&candidate](CblImportEntry &entry) { + entry.choice = candidate; + entry.changed = entry.match.accepted != entry.choice; + }); +} + +void CblImportModel::useOriginalMatch(const QList &rows) +{ + changeRows(rows, [](CblImportEntry &entry) { + entry.choice = entry.match.accepted; + entry.changed = false; + }); +} + +void CblImportModel::importAsMissing(const QList &rows) +{ + changeRows(rows, [](CblImportEntry &entry) { + entry.choice.reset(); + // a book that found nothing is already missing, so nothing changes + entry.changed = entry.match.accepted || !entry.match.candidates.isEmpty(); + }); +} + +QString CblImportModel::bookTitle(const CblBook &book) +{ + if (book.series.isEmpty()) + return book.fileName; + + auto title = book.series; + if (!book.number.isEmpty()) + title += QStringLiteral(" #") + book.number; + const auto &volume = book.volume.isEmpty() ? book.year : book.volume; + if (!volume.isEmpty()) + title += QStringLiteral(" (") + volume + QStringLiteral(")"); + return title; +} + +QString CblImportModel::fileName(const CblLibraryComic &comic) +{ + return comic.path.section('/', -1); +} + +QString CblImportModel::comicText(const CblImportEntry &entry) const +{ + if (entry.choice) { + const auto *comic = std::get_if(&*entry.choice); + if (comic != nullptr && comic->hasFile) + return fileName(*comic); + return tr("Already missing in your library"); + } + + if (!entry.changed && !entry.match.candidates.isEmpty()) + return tr("%n possible comic(s)", "", entry.match.candidates.size()); + return tr("Not in library"); +} + +bool CblImportModel::comicTextIsNote(const CblImportEntry &entry) const +{ + const auto *comic = entry.choice ? std::get_if(&*entry.choice) : nullptr; + return comic == nullptr || !comic->hasFile; +} + +void CblImportModel::changeRows(const QList &rows, const std::function &change) +{ + for (const auto row : rows) { + if (row < 0 || row >= importEntries.size()) + continue; + change(importEntries[row]); + emit dataChanged(index(row, 0), index(row, ColumnCount - 1)); + } +} + +CblImportFilterModel::CblImportFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + setFilterKeyColumn(-1); +} + +void CblImportFilterModel::setStatusFilter(std::optional status) +{ + statusFilter = status; + invalidateRowsFilter(); +} + +bool CblImportFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (sourceParent.isValid()) + return true; + if (statusFilter && sourceModel()->index(sourceRow, 0, sourceParent).data(CblImportModel::StatusRole).toInt() != int(*statusFilter)) + return false; + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); +} diff --git a/YACReaderLibrary/cbl/cbl_import_model.h b/YACReaderLibrary/cbl/cbl_import_model.h new file mode 100644 index 000000000..bbad9cdc5 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_import_model.h @@ -0,0 +1,106 @@ +#ifndef CBL_IMPORT_MODEL_H +#define CBL_IMPORT_MODEL_H + +#include "cbl_matcher.h" +#include "cbl_reader.h" + +#include +#include +#include + +#include +#include + +struct CblImportEntry { + CblBook book; + CblBookMatch match; + // what the import uses; it starts as the accepted match + std::optional choice; + bool changed = false; + // in a list: what the entry refers to when the view opens + std::optional current; +}; + +enum class CblImportStatus { + Matched, + Ambiguous, + Missing, + Changed, +}; + +// one row per entry; each entry has one child row, which holds its detail +class CblImportModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Column { + NumberColumn, + BookColumn, + ComicColumn, + StatusColumn, + ActionColumn, + ColumnCount, + }; + + enum Roles { + StatusRole = Qt::UserRole + 1, + }; + + explicit CblImportModel(QObject *parent = nullptr); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void setEntries(const QList &entries); + const QList &entries() const { return importEntries; } + void setWarningColor(const QColor &color); + + static bool isDetail(const QModelIndex &index); + CblImportStatus status(int row) const; + int count(CblImportStatus status) const; + // the entries whose choice is a comic with a file + int withComicCount() const; + // the Ambiguous and Missing entries, in order + QList problemRows() const; + QList> choices() const; + + void choose(int row, const CblCandidate &candidate); + void useOriginalMatch(const QList &rows); + void importAsMissing(const QList &rows); + + static QString bookTitle(const CblBook &book); + static QString fileName(const CblLibraryComic &comic); + +private: + QString comicText(const CblImportEntry &entry) const; + bool comicTextIsNote(const CblImportEntry &entry) const; + void changeRows(const QList &rows, const std::function &change); + + QList importEntries; + QColor warningColor; +}; + +class CblImportFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit CblImportFilterModel(QObject *parent = nullptr); + + // nothing shows every status + void setStatusFilter(std::optional status); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + +private: + std::optional statusFilter; +}; + +#endif diff --git a/YACReaderLibrary/cbl/cbl_importer.cpp b/YACReaderLibrary/cbl/cbl_importer.cpp new file mode 100644 index 000000000..611f3f42b --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_importer.cpp @@ -0,0 +1,144 @@ +#include "cbl_importer.h" + +#include "QsLog.h" +#include "db_helper.h" +#include "overloaded.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +QVariant text(const QString &value) +{ + const auto trimmed = value.trimmed(); + return trimmed.isEmpty() ? QVariant() : QVariant(trimmed); +} + +QString comicVineIssueId(const CblBook &book) +{ + static const QRegularExpression digits(R"(^\d+$)"); + for (const auto &ids : book.databases) { + const bool comicVine = ids.name.compare(QLatin1StringView("cv"), Qt::CaseInsensitive) == 0 || ids.name.compare(QLatin1StringView("comicvine"), Qt::CaseInsensitive) == 0; + if (comicVine && digits.match(ids.issueId.trimmed()).hasMatch()) + return ids.issueId.trimmed(); + } + return { }; +} + +// dd/mm/yyyy with the parts that are known; a year alone never gets a day or month +QVariant date(const CblBook &book) +{ + const auto parts = book.coverDate.trimmed().split('-'); + if (parts.size() == 3 && parts.at(0).toInt() > 0) + return QString("%1/%2/%3").arg(parts.at(2), parts.at(1), parts.at(0)); + if (parts.size() == 2 && parts.at(0).toInt() > 0) + return QString("/%1/%2").arg(parts.at(1), parts.at(0)); + + const auto year = book.year.trimmed().toInt(); + if (year > 0) + return QString("//%1").arg(year); + return { }; +} + +// the books of one import that are the same issue; empty when a book cannot be compared +QString sameIssueKey(const CblBook &book) +{ + if (const auto id = comicVineIssueId(book); !id.isEmpty()) + return "cv:" + id; + if (book.series.trimmed().isEmpty() && book.number.trimmed().isEmpty()) + return { }; + return QStringList { book.series.trimmed().toCaseFolded(), book.number.trimmed().toCaseFolded(), book.volume.trimmed().toCaseFolded(), book.year.trimmed() }.join(QChar(0x1F)); +} + +} + +Placeholder placeholderFromCblBook(const CblBook &book) +{ + Placeholder placeholder; + placeholder.series = text(book.series); + placeholder.number = text(book.number); + placeholder.volume = text(book.volume); + placeholder.format = text(book.format); + placeholder.date = date(book); + placeholder.comicVineID = text(comicVineIssueId(book)); + return placeholder; +} + +QString cblListName(const CblReadingList &list, const QString &filePath) +{ + const auto name = list.name.trimmed(); + return name.isEmpty() ? QFileInfo(filePath).completeBaseName() : name; +} + +std::optional importCblReadingList(const QString &name, const CblReadingList &list, const QList> &choices, QSqlDatabase &db) +{ + const auto &books = list.books; + if (books.size() != choices.size()) + return std::nullopt; + const auto sourceFormat = list.format == CblReadingList::Json ? QStringLiteral("json") : QStringLiteral("xml"); + + db.transaction(); + const auto fail = [&db](const QSqlQuery &query) -> std::optional { + QLOG_ERROR() << "Unable to import a CBL reading list:" << query.lastError().text(); + db.rollback(); + return std::nullopt; + }; + + QSqlQuery readingList(db); + readingList.prepare("INSERT INTO reading_list (name) VALUES (:name)"); + readingList.bindValue(":name", name); + if (!readingList.exec()) + return fail(readingList); + const auto readingListId = ReadingListId(readingList.lastInsertId().toULongLong()); + + QSqlQuery entry(db); + entry.prepare("INSERT INTO reading_list_entry (reading_list_id, ordering, comic_info_id, placeholder_id, source, source_format) " + "VALUES (:reading_list_id, :ordering, :comic_info_id, :placeholder_id, :source, :source_format)"); + + QHash newPlaceholders; + for (qsizetype i = 0; i < books.size(); i++) { + QVariant comicInfoId; + QVariant placeholderId; + if (const auto &choice = choices.at(i)) { + std::visit(overloaded { + [&](const CblLibraryComic &comic) { comicInfoId = comic.id.value(); }, + [&](const CblLibraryPlaceholder &placeholder) { placeholderId = placeholder.id.value(); }, + }, + *choice); + } else { + const auto key = sameIssueKey(books.at(i)); + std::optional created; + if (!key.isEmpty() && newPlaceholders.contains(key)) { + created = newPlaceholders.value(key); + } else { + created = DBHelper::insertPlaceholder(placeholderFromCblBook(books.at(i)), db); + if (!created) { + db.rollback(); + return std::nullopt; + } + if (!key.isEmpty()) + newPlaceholders.insert(key, *created); + } + placeholderId = created->value(); + } + + entry.bindValue(":reading_list_id", readingListId.value()); + entry.bindValue(":ordering", i); + entry.bindValue(":comic_info_id", comicInfoId); + entry.bindValue(":placeholder_id", placeholderId); + const bool keepSource = !books.at(i).source.isEmpty(); + entry.bindValue(":source", keepSource ? QVariant(books.at(i).source) : QVariant()); + entry.bindValue(":source_format", keepSource ? QVariant(sourceFormat) : QVariant()); + if (!entry.exec()) + return fail(entry); + } + + if (!db.commit()) + return fail(readingList); + return readingListId; +} diff --git a/YACReaderLibrary/cbl/cbl_importer.h b/YACReaderLibrary/cbl/cbl_importer.h new file mode 100644 index 000000000..92837f637 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_importer.h @@ -0,0 +1,29 @@ +#ifndef CBL_IMPORTER_H +#define CBL_IMPORTER_H + +#include "cbl_matcher.h" +#include "cbl_reader.h" +#include "library_ids.h" +#include "placeholder.h" + +#include +#include + +#include + +class QSqlDatabase; + +// the placeholder of a book that matched nothing: series, number, volume, format, the date (cover date, else a +// year alone as "//yyyy"), and the Comic Vine issue id; the other provider ids stay in the source of its entries +Placeholder placeholderFromCblBook(const CblBook &book); + +// the name in the file, or else the file name without its extension +QString cblListName(const CblReadingList &list, const QString &filePath); + +// creates a top-level reading list `name` with one entry per book of `list`, in order, in one transaction; `choices[i]` +// is what book i refers to. A book without a choice gets a new placeholder, which the books of this import that are the +// same issue share (the same Comic Vine id, or else the same series, number, volume, and year). Every entry keeps the +// source of its book. Nothing is written when any step fails. +std::optional importCblReadingList(const QString &name, const CblReadingList &list, const QList> &choices, QSqlDatabase &db); + +#endif diff --git a/YACReaderLibrary/cbl/cbl_list_match.cpp b/YACReaderLibrary/cbl/cbl_list_match.cpp new file mode 100644 index 000000000..8e5868a00 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_list_match.cpp @@ -0,0 +1,173 @@ +#include "cbl_list_match.h" + +#include "QsLog.h" +#include "cbl_importer.h" +#include "db_helper.h" + +#include +#include +#include +#include + +namespace { + +// the entry as it is now +struct StoredEntry { + QVariant comicInfoId; + QVariant placeholderId; + bool hasFile = false; + std::optional source; +}; + +std::optional loadEntry(ListItemId id, QSqlDatabase &db) +{ + const bool label = id.listKind == ListKind::Label; + QSqlQuery query(db); + query.prepare(QString("SELECT e.comic_info_id, e.placeholder_id, EXISTS (SELECT 1 FROM comic WHERE comicInfoId = e.comic_info_id), %1 FROM %2 e WHERE e.id = :id") + .arg(label ? "NULL, NULL" : "e.source, e.source_format", label ? "label_entry" : "reading_list_entry")); + query.bindValue(":id", id.id); + if (!query.exec()) { + QLOG_ERROR() << "Unable to read a list entry:" << query.lastError().text(); + return std::nullopt; + } + if (!query.next()) + return std::nullopt; + + StoredEntry entry; + entry.comicInfoId = query.value(0); + entry.placeholderId = query.value(1); + entry.hasFile = query.value(2).toBool(); + if (!query.value(3).isNull()) + entry.source = EntrySource { query.value(3).toString(), query.value(4).toString() }; + return entry; +} + +bool makeMissing(ListItemId id, const StoredEntry &entry, QSqlDatabase &db) +{ + Placeholder placeholder; + const auto book = entry.source ? readCblBook(entry.source->text, entry.source->format) : std::nullopt; + if (book) { + placeholder = placeholderFromCblBook(*book); + } else { + const auto info = DBHelper::loadComicInfo(ComicInfoId(entry.comicInfoId.toULongLong()), db); + if (!info) + return false; + static_cast(placeholder) = *info; + } + + const auto placeholderId = DBHelper::insertPlaceholder(placeholder, db); + if (!placeholderId) + return false; + + QSqlQuery update(db); + update.prepare(QString("UPDATE %1 SET comic_info_id = NULL, placeholder_id = :placeholder_id, last_known_name = NULL, last_known_path = NULL WHERE id = :id") + .arg(id.listKind == ListKind::Label ? "label_entry" : "reading_list_entry")); + update.bindValue(":placeholder_id", placeholderId->value()); + update.bindValue(":id", id.id); + if (!update.exec() || update.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to make an entry missing:" << update.lastError().text(); + return false; + } + return true; +} + +} + +CblListEntry cblListEntry(const ComicRecord &record, const ListEntry &entry) +{ + CblListEntry result; + result.id = entry.id; + result.listId = entry.listId; + + const auto &metadata = recordMetadata(record); + if (const auto *placeholder = recordPlaceholder(record)) { + CblLibraryPlaceholder current; + static_cast(current) = cblLibraryMetadata(metadata); + current.id = placeholder->id; + current.coverKey = placeholder->coverKey.value_or(QString()); + result.current = current; + } else if (const auto *info = recordInfo(record)) { + CblLibraryComic current; + static_cast(current) = cblLibraryMetadata(metadata); + current.id = info->id; + current.hash = info->hash; + if (const auto *comic = recordComic(record)) { + current.hasFile = true; + current.path = comic->file.path; + } + result.current = current; + } + + const auto book = entry.source ? readCblBook(entry.source->text, entry.source->format) : std::nullopt; + result.book = book.value_or(cblBookOf(metadata)); + return result; +} + +CblExportBook cblExportBook(const ComicRecord &record, const ListEntry &entry) +{ + CblExportBook result; + result.book = cblListEntry(record, entry).book; + if (entry.source) { + result.source = entry.source->text; + result.sourceFormat = entry.source->format; + } + return result; +} + +CblBook cblBookOf(const DescriptiveComicMetadata &metadata) +{ + CblBook book; + book.series = metadata.series.toString(); + book.number = metadata.number.toString(); + book.volume = metadata.volume.toString(); + book.format = metadata.format.toString(); + if (const auto year = cblYearOfDate(metadata.date.toString()); year > 0) + book.year = QString::number(year); + // a full d/m/yyyy date as yyyy-mm-dd + if (const auto parts = metadata.date.toString().split('/'); parts.size() == 3) { + if (const QDate date(parts[2].toInt(), parts[1].toInt(), parts[0].toInt()); date.isValid()) + book.coverDate = date.toString(Qt::ISODate); + } + if (const auto issueId = metadata.comicVineID.toString().trimmed(); !issueId.isEmpty()) + book.databases.append({ QStringLiteral("cv"), QString(), issueId }); + return book; +} + +CblLibraryMetadata cblLibraryMetadata(const DescriptiveComicMetadata &metadata) +{ + CblLibraryMetadata result; + result.series = metadata.series.toString(); + result.number = metadata.number.toString(); + result.volume = metadata.volume.toString(); + result.date = metadata.date.toString(); + result.format = metadata.format.toString(); + result.comicVineId = metadata.comicVineID.toString(); + return result; +} + +bool applyCblListChanges(const QList &changes, const QString &libraryDataPath, QSqlDatabase &db, ComicMatchFiles &files) +{ + for (const auto &change : changes) { + const auto entry = loadEntry(change.entry, db); + if (!entry) + return false; + + bool applied = true; + if (change.comic) { + const auto comicInfoId = *change.comic; + if (!entry->placeholderId.isNull()) + applied = matchPlaceholderToComic(PlaceholderId(entry->placeholderId.toULongLong()), comicInfoId, libraryDataPath, db, files); + else if (entry->comicInfoId.toULongLong() == comicInfoId.value()) + continue; + else if (!entry->hasFile) + applied = matchDeletedComicToComic(ComicInfoId(entry->comicInfoId.toULongLong()), comicInfoId, db, files); + else + applied = giveComicToEntry(change.entry, comicInfoId, db); + } else if (entry->placeholderId.isNull() && entry->hasFile) { + applied = makeMissing(change.entry, *entry, db); + } + if (!applied) + return false; + } + return true; +} diff --git a/YACReaderLibrary/cbl/cbl_list_match.h b/YACReaderLibrary/cbl/cbl_list_match.h new file mode 100644 index 000000000..172349061 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_list_match.h @@ -0,0 +1,46 @@ +#ifndef CBL_LIST_MATCH_H +#define CBL_LIST_MATCH_H + +#include "cbl_matcher.h" +#include "cbl_reader.h" +#include "cbl_writer.h" +#include "comic_record.h" +#include "library_comic_match.h" + +#include + +#include + +class QSqlDatabase; + +// an entry of a reading list or label in the match view +struct CblListEntry { + ListItemId id { }; + qulonglong listId = 0; + // what it refers to when the view opens: a comic (with or without a file) or a placeholder + CblCandidate current; + // its CBL book, else the values of what it refers to + CblBook book; +}; + +CblListEntry cblListEntry(const ComicRecord &record, const ListEntry &entry); +// an entry of a reading list in an export: its book and its source +CblExportBook cblExportBook(const ComicRecord &record, const ListEntry &entry); +// the values of a comic or missing entry as a book, for matching and the views +CblBook cblBookOf(const DescriptiveComicMetadata &metadata); +CblLibraryMetadata cblLibraryMetadata(const DescriptiveComicMetadata &metadata); + +// what Apply does to one entry +struct CblListChange { + ListItemId entry { }; + // the comic with a file that it gets; nothing makes it missing + std::optional comic; +}; + +// In order, each change uses what its entry refers to when it runs: a missing entry or deleted comic is matched (every +// entry of it, in every list), a comic entry only changes itself, and "make missing" gives a comic entry a new +// placeholder from its CBL source, else from the comic's metadata. No transaction: the caller runs it in its own. +// False when an entry is gone or a write fails. +bool applyCblListChanges(const QList &changes, const QString &libraryDataPath, QSqlDatabase &db, ComicMatchFiles &files); + +#endif diff --git a/YACReaderLibrary/cbl/cbl_match_comic_dialog.cpp b/YACReaderLibrary/cbl/cbl_match_comic_dialog.cpp new file mode 100644 index 000000000..3879b8064 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_match_comic_dialog.cpp @@ -0,0 +1,107 @@ +#include "cbl_match_comic_dialog.h" + +#include "cbl_entry_detail.h" +#include "cbl_find_comic_page.h" +#include "cbl_list_match.h" +#include "data_base_management.h" +#include "descriptive_comic_metadata.h" +#include "yacreader_global.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +struct LoadedLibrary { + bool error = false; + QList folders; + QString startFolderPath; +}; + +LoadedLibrary loadLibrary(const CblBook &book, const QString &libraryPath) +{ + LoadedLibrary result; + QString connectionName; + { + auto db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + connectionName = db.connectionName(); + + const auto comics = db.isOpen() ? CblMatcher::loadLibraryComics(db) : std::nullopt; + const auto folders = db.isOpen() ? CblFindComicPage::loadFolders(db) : std::nullopt; + if (!comics || !folders) { + result.error = true; + } else { + result.folders = *folders; + // only comics with a file can be chosen + QList withFile; + std::copy_if(comics->cbegin(), comics->cend(), std::back_inserter(withFile), [](const CblLibraryComic &comic) { return comic.hasFile; }); + const CblMatcher matcher(withFile); + CblImportEntry entry; + entry.book = book; + entry.match = matcher.match(book); + result.startFolderPath = CblFindComicPage::startFolderPath(entry, matcher); + } + } + if (!connectionName.isEmpty()) + QSqlDatabase::removeDatabase(connectionName); + return result; +} + +} + +CblMatchComicDialog::CblMatchComicDialog(const DescriptiveComicMetadata &metadata, const QString &libraryPath, std::optional folderId, QWidget *parent) + : QDialog(parent) +{ + setWindowTitle(tr("Match to library comic")); + resize(960, 600); + + messageLabel = new QLabel(tr("Loading the library...")); + messageLabel->setAlignment(Qt::AlignCenter); + messageLabel->setWordWrap(true); + + findPage = new CblFindComicPage(libraryPath, CblFindComicPage::Mode::OneEntry); + findPage->setWarningColor(cblWarningColor(palette())); + connect(findPage, &CblFindComicPage::backRequested, this, &QDialog::reject); + connect(findPage, &CblFindComicPage::comicChosen, this, [this](const CblLibraryComic &comic) { + chosen = comic; + accept(); + }); + + pages = new QStackedWidget; + pages->addWidget(messageLabel); + pages->addWidget(findPage); + + auto layout = new QVBoxLayout(this); + layout->addWidget(pages); + + const auto book = cblBookOf(metadata); + auto result = std::make_shared(); + auto thread = QThread::create([result, book, libraryPath] { *result = loadLibrary(book, libraryPath); }); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, this, [this, result, book, folderId] { + if (result->error) { + messageLabel->setText(tr("The library cannot be read.")); + return; + } + CblImportEntry entry; + entry.book = book; + findPage->setFolders(result->folders); + if (folderId) + findPage->limitToFolder(*folderId); + findPage->showEntry(entry, 0, 1, CblImportStatus::Missing, result->startFolderPath, -1, 0, false, false); + pages->setCurrentWidget(findPage); + }); + thread->start(); +} + +std::optional CblMatchComicDialog::chosenComic() const +{ + return chosen; +} diff --git a/YACReaderLibrary/cbl/cbl_match_comic_dialog.h b/YACReaderLibrary/cbl/cbl_match_comic_dialog.h new file mode 100644 index 000000000..579a78b9d --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_match_comic_dialog.h @@ -0,0 +1,34 @@ +#ifndef CBL_MATCH_COMIC_DIALOG_H +#define CBL_MATCH_COMIC_DIALOG_H + +#include "cbl_matcher.h" + +#include + +#include + +class CblFindComicPage; +class QLabel; +class QStackedWidget; +struct DescriptiveComicMetadata; + +// chooses the library comic of one missing entry or deleted comic, with the Find comic page of the import; with a +// folder, only among the comics of that folder +class CblMatchComicDialog : public QDialog +{ + Q_OBJECT + +public: + CblMatchComicDialog(const DescriptiveComicMetadata &metadata, const QString &libraryPath, std::optional folderId = std::nullopt, QWidget *parent = nullptr); + + // set when the dialog is accepted + std::optional chosenComic() const; + +private: + QStackedWidget *pages; + QLabel *messageLabel; + CblFindComicPage *findPage; + std::optional chosen; +}; + +#endif diff --git a/YACReaderLibrary/cbl/cbl_matcher.cpp b/YACReaderLibrary/cbl/cbl_matcher.cpp new file mode 100644 index 000000000..e20c77041 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_matcher.cpp @@ -0,0 +1,397 @@ +#include "cbl_matcher.h" + +#include "QsLog.h" +#include "folder_placeholder_match.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +// "v1", "Vol. 2", "Volume 3" inside a series name +const QRegularExpression &volumeInName() +{ + static const QRegularExpression expression(R"(\bv(ol(ume)?)?\.?\s?\d+\b\s*)", QRegularExpression::CaseInsensitiveOption); + return expression; +} + +// "Batman (2016)": Comic Vine names a volume by its start year +const QRegularExpression &yearInName() +{ + static const QRegularExpression expression(R"(^(.*\S)\s*\((\d{4})\)\s*$)"); + return expression; +} + +QString sameSeries(const QString &series) +{ + return series.simplified().toCaseFolded(); +} + +QString seriesWithoutVolume(const QString &series) +{ + auto result = series; + result.remove(volumeInName()); + return sameSeries(result); +} + +// letters and digits of any script, without the words "the" and "and"; only Latin letters lose their accents, +// because in other scripts a mark can make another letter (ボ and ホ, й and и) +QString lettersAndDigits(const QString &series) +{ + const auto decomposed = seriesWithoutVolume(series).normalized(QString::NormalizationForm_KD); + QString result; + QString word; + const auto endWord = [&] { + if (word != QLatin1StringView("the") && word != QLatin1StringView("and")) + result += word; + word.clear(); + }; + auto latinBase = false; + for (const auto c : decomposed) { + if (c.isMark()) { + if (!latinBase) + word += c; + continue; + } + latinBase = c.script() == QChar::Script_Latin; + if (c.isLetterOrNumber()) + word += c; + else + endWord(); + } + endWord(); + return result; +} + +// numbers are text; only the case and leading zeros do not count +QString number(const QString &number) +{ + static const QRegularExpression leadingZeros(R"(^(-?)0+(?=\d))"); + auto result = number.trimmed().toCaseFolded(); + result.replace(leadingZeros, QStringLiteral("\\1")); + return result; +} + +int yearOfDate(const QString &date) +{ + const auto year = date.section('/', -1).trimmed().toInt(); + return year > 0 ? year : 0; +} + +int bookYear(const CblBook &book) +{ + auto year = book.year.toInt(); + if (year <= 0) + year = book.coverDate.section('-', 0, 0).toInt(); + return year > 0 ? year : 0; +} + +// the volume, or the year in a "Series (Year)" name +QString effectiveVolume(const CblLibraryMetadata &metadata) +{ + if (!metadata.volume.trimmed().isEmpty()) + return metadata.volume.trimmed(); + const auto year = yearInName().match(metadata.series); + return year.hasMatch() ? year.captured(2) : QString(); +} + +QString key(const QString &series, const QString &issueNumber) +{ + return series + QChar(0x1F) + number(issueNumber); +} + +// the keys of a series at a name level; "Series (Year)" gives one key per known year +QStringList seriesKeys(CblMatchLevel level, const QString &series, const QString &volume) +{ + switch (level) { + case CblMatchLevel::SameSeries: + return { sameSeries(series) }; + case CblMatchLevel::SeriesWithoutVolume: + return { seriesWithoutVolume(series) }; + case CblMatchLevel::LettersAndDigits: + return { lettersAndDigits(series) }; + case CblMatchLevel::SeriesWithYear: { + QStringList keys; + const auto year = yearInName().match(series); + if (year.hasMatch()) + keys.append(lettersAndDigits(year.captured(1)) + QChar(0x1F) + year.captured(2)); + if (!volume.trimmed().isEmpty()) + keys.append(lettersAndDigits(series) + QChar(0x1F) + volume.trimmed().toCaseFolded()); + return keys; + } + case CblMatchLevel::ComicVineId: + break; + } + return { }; +} + +constexpr CblMatchLevel nameLevels[] = { CblMatchLevel::SameSeries, CblMatchLevel::SeriesWithoutVolume, CblMatchLevel::LettersAndDigits, CblMatchLevel::SeriesWithYear }; + +bool isComicVine(const CblDatabaseIds &ids) +{ + return ids.name.compare(QLatin1StringView("cv"), Qt::CaseInsensitive) == 0 || ids.name.compare(QLatin1StringView("comicvine"), Qt::CaseInsensitive) == 0; +} + +template +CblMatcher::Index buildIndex(const QList &items) +{ + CblMatcher::Index index; + for (qsizetype i = 0; i < items.size(); i++) { + const auto &item = items.at(i); + if (const auto id = item.comicVineId.trimmed(); !id.isEmpty()) + index.byComicVineId[id].append(i); + if (item.series.trimmed().isEmpty()) + continue; + for (int level = 0; level < 4; level++) { + for (const auto &seriesKey : seriesKeys(nameLevels[level], item.series, item.volume)) { + auto &positions = index.byName[level][key(seriesKey, item.number)]; + if (!positions.contains(i)) + positions.append(i); + if (level < 3) + index.bySeries[level][seriesKey].append(i); + } + } + } + return index; +} + +// keeps the items that pass, unless none does +template +void narrow(QList &items, const std::function &passes) +{ + QList passing; + for (const auto &item : std::as_const(items)) { + if (passes(item)) + passing.append(item); + } + if (!passing.isEmpty()) + items = passing; +} + +// a comic with a file wins over a deleted one +void preferFiles(QList &comics) +{ + narrow(comics, [](const CblLibraryComic &comic) { return comic.hasFile; }); +} + +void preferFiles(QList &) +{ +} + +template +void breakTies(const CblBook &book, QList &items) +{ + if (const auto volume = book.volume.trimmed().toCaseFolded(); !volume.isEmpty()) + narrow(items, [&](const Item &item) { return effectiveVolume(item).toCaseFolded() == volume; }); + if (const auto year = bookYear(book); year > 0) + narrow(items, [&](const Item &item) { const auto itemYear = yearOfDate(item.date); return itemYear > 0 && std::abs(itemYear - year) <= 1; }); + if (const auto format = book.format.trimmed(); !format.isEmpty()) + narrow(items, [&](const Item &item) { return item.format.trimmed().compare(format, Qt::CaseInsensitive) == 0; }); + preferFiles(items); +} + +// the volume is the same or the year is within one, and no known volume or year disagrees +bool agrees(const CblBook &book, const CblLibraryMetadata &metadata) +{ + const auto bookVolume = book.volume.trimmed(); + const auto itemVolume = effectiveVolume(metadata); + const auto volumeKnown = !bookVolume.isEmpty() && !itemVolume.isEmpty(); + const auto volumeSame = volumeKnown && bookVolume.compare(itemVolume, Qt::CaseInsensitive) == 0; + + const auto year = bookYear(book); + const auto itemYear = yearOfDate(metadata.date); + const auto yearKnown = year > 0 && itemYear > 0; + const auto yearClose = yearKnown && std::abs(year - itemYear) <= 1; + + return (volumeSame || yearClose) && (!volumeKnown || volumeSame) && (!yearKnown || yearClose); +} + +// the match of `book` among `items`, or nothing when no item fits it +template +std::optional matchIn(const CblBook &book, const QList &items, const CblMatcher::Index &index) +{ + const auto result = [&](const QList &found, CblMatchLevel level, bool needsAgreement) { + QList fitting; + for (const auto position : found) + fitting.append(items.at(position)); + if (needsAgreement) + breakTies(book, fitting); + else + preferFiles(fitting); + + CblBookMatch match; + match.level = level; + for (const auto &item : std::as_const(fitting)) + match.candidates.append(item); + if (fitting.size() == 1 && (!needsAgreement || agrees(book, fitting.first()))) + match.accepted = fitting.first(); + return match; + }; + + QList found; + for (const auto &ids : book.databases) { + if (!isComicVine(ids) || ids.issueId.isEmpty()) + continue; + for (const auto position : index.byComicVineId.value(ids.issueId)) { + if (!found.contains(position)) + found.append(position); + } + } + if (!found.isEmpty()) + return result(found, CblMatchLevel::ComicVineId, false); + + if (book.series.trimmed().isEmpty()) + return std::nullopt; + + for (int level = 0; level < 4; level++) { + for (const auto &seriesKey : seriesKeys(nameLevels[level], book.series, book.volume)) { + for (const auto position : index.byName[level].value(key(seriesKey, book.number))) { + if (!found.contains(position)) + found.append(position); + } + } + if (!found.isEmpty()) + return result(found, nameLevels[level], true); + } + return std::nullopt; +} + +} + +CblMatcher::CblMatcher(const QList &comics, const QList &placeholders) + : comics(comics), placeholders(placeholders), comicIndex(buildIndex(comics)), placeholderIndex(buildIndex(placeholders)) +{ +} + +QList CblMatcher::closestSeries(const CblBook &book) const +{ + if (book.series.trimmed().isEmpty()) + return { }; + + for (int level = 0; level < 3; level++) { + QList found; + for (const auto &seriesKey : seriesKeys(nameLevels[level], book.series, book.volume)) { + for (const auto position : comicIndex.bySeries[level].value(seriesKey)) + found.append(comics.at(position)); + } + if (found.isEmpty()) + continue; + if (const auto volume = book.volume.trimmed().toCaseFolded(); !volume.isEmpty()) + narrow(found, [&](const CblLibraryComic &comic) { return effectiveVolume(comic).toCaseFolded() == volume; }); + return found; + } + return { }; +} + +// an accepted comic wins; a comic that is only a candidate does not hide a placeholder of the same issue +CblBookMatch CblMatcher::match(const CblBook &book) const +{ + const auto comicMatch = matchIn(book, comics, comicIndex); + if (comicMatch && comicMatch->accepted) + return *comicMatch; + + const auto placeholderMatch = matchIn(book, placeholders, placeholderIndex); + if (!comicMatch) + return placeholderMatch.value_or(CblBookMatch { }); + if (!placeholderMatch) + return *comicMatch; + + auto result = *comicMatch; + result.candidates += placeholderMatch->candidates; + if (placeholderMatch->accepted) { + result.accepted = placeholderMatch->accepted; + result.level = placeholderMatch->level; + } + return result; +} + +std::optional> CblMatcher::loadLibraryComics(QSqlDatabase &db) +{ + QSqlQuery query(db); + query.setForwardOnly(true); + if (!query.exec("SELECT ci.id, (SELECT c.path FROM comic c WHERE c.comicInfoId = ci.id ORDER BY c.id LIMIT 1), ci.series, ci.number, ci.volume, ci.date, ci.format, ci.comicVineID, ci.hash " + "FROM comic_info ci " + "WHERE COALESCE(ci.series, '') <> '' OR COALESCE(ci.comicVineID, '') <> ''")) { + QLOG_ERROR() << "Unable to load comics for CBL matching:" << query.lastError().text(); + return std::nullopt; + } + + QList comics; + while (query.next()) { + CblLibraryComic comic; + comic.id = ComicInfoId(query.value(0).toULongLong()); + comic.hasFile = !query.value(1).isNull(); + comic.path = query.value(1).toString(); + comic.series = query.value(2).toString(); + comic.number = query.value(3).toString(); + comic.volume = query.value(4).toString(); + comic.date = query.value(5).toString(); + comic.format = query.value(6).toString(); + comic.comicVineId = query.value(7).toString(); + comic.hash = query.value(8).toString(); + comics.append(comic); + } + return comics; +} + +std::optional> CblMatcher::loadLibraryPlaceholders(QSqlDatabase &db) +{ + QSqlQuery query(db); + query.setForwardOnly(true); + if (!query.exec("SELECT p.id, p.series, p.number, p.volume, p.date, p.format, p.comicVineID, p.coverKey " + "FROM placeholder p " + "WHERE COALESCE(p.series, '') <> '' OR COALESCE(p.comicVineID, '') <> ''")) { + QLOG_ERROR() << "Unable to load placeholders for CBL matching:" << query.lastError().text(); + return std::nullopt; + } + + QList placeholders; + while (query.next()) { + CblLibraryPlaceholder placeholder; + placeholder.id = PlaceholderId(query.value(0).toULongLong()); + placeholder.series = query.value(1).toString(); + placeholder.number = query.value(2).toString(); + placeholder.volume = query.value(3).toString(); + placeholder.date = query.value(4).toString(); + placeholder.format = query.value(5).toString(); + placeholder.comicVineId = query.value(6).toString(); + placeholder.coverKey = query.value(7).toString(); + placeholders.append(placeholder); + } + return placeholders; +} + +QString cblComicVineIssueId(const CblBook &book) +{ + for (const auto &ids : book.databases) { + if (isComicVine(ids) && !ids.issueId.isEmpty()) + return ids.issueId; + } + return { }; +} + +int cblBookYear(const CblBook &book) +{ + return bookYear(book); +} + +int cblYearOfDate(const QString &date) +{ + return yearOfDate(date); +} + +QString cblEffectiveVolume(const CblLibraryMetadata &metadata) +{ + return effectiveVolume(metadata); +} + +bool cblSameNumber(const QString &a, const QString &b) +{ + return sameIssueNumber(a, b); +} diff --git a/YACReaderLibrary/cbl/cbl_matcher.h b/YACReaderLibrary/cbl/cbl_matcher.h new file mode 100644 index 000000000..a565d23c1 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_matcher.h @@ -0,0 +1,107 @@ +#ifndef CBL_MATCHER_H +#define CBL_MATCHER_H + +#include "cbl_reader.h" +#include "library_ids.h" + +#include +#include +#include + +#include +#include + +class QSqlDatabase; + +// the fields matching reads, from a comic_info or a placeholder +struct CblLibraryMetadata { + QString series; + QString number; + QString volume; + QString date; + QString format; + QString comicVineId; + + friend bool operator==(const CblLibraryMetadata &, const CblLibraryMetadata &) = default; +}; + +// deleted comics (no file) are included +struct CblLibraryComic : CblLibraryMetadata { + ComicInfoId id; + bool hasFile = false; + // the path of one of its comics (the lowest id); empty when it has no file + QString path; + QString hash; + + friend bool operator==(const CblLibraryComic &, const CblLibraryComic &) = default; +}; + +struct CblLibraryPlaceholder : CblLibraryMetadata { + PlaceholderId id; + QString coverKey; + + friend bool operator==(const CblLibraryPlaceholder &, const CblLibraryPlaceholder &) = default; +}; + +using CblCandidate = std::variant; + +// from the most to the least certain; the first level that finds a candidate is used +enum class CblMatchLevel { + ComicVineId, + SameSeries, + SeriesWithoutVolume, + LettersAndDigits, + SeriesWithYear, +}; + +struct CblBookMatch { + // what the import uses without asking the user + std::optional accepted; + // what fits the book at `level`, best first; the user chooses when nothing is accepted + QList candidates; + std::optional level; +}; + +// an accepted comic wins; else an accepted placeholder; the candidates list the comics, then the placeholders +class CblMatcher +{ +public: + CblMatcher(const QList &comics, const QList &placeholders = { }); + + CblBookMatch match(const CblBook &book) const; + // the comics of the series that the name levels find for the book series with any number (the same series, then + // without the volume, then letters and digits); only those of the book's volume when there are any + QList closestSeries(const CblBook &book) const; + + // every comic_info with a series or a Comic Vine id; nothing if the query fails + static std::optional> loadLibraryComics(QSqlDatabase &db); + // every placeholder with a series or a Comic Vine id; nothing if the query fails + static std::optional> loadLibraryPlaceholders(QSqlDatabase &db); + + struct Index { + QHash> byComicVineId; + // per name level: series key and number -> positions + QHash> byName[4]; + // per series level (same series, without the volume, letters and digits): series key -> positions + QHash> bySeries[3]; + }; + +private: + QList comics; + QList placeholders; + Index comicIndex; + Index placeholderIndex; +}; + +// the values that matching compares, for the UI +QString cblComicVineIssueId(const CblBook &book); +// the year, else the year of the cover date; 0 when unknown +int cblBookYear(const CblBook &book); +// the year of a dd/mm/yyyy date; 0 when unknown +int cblYearOfDate(const QString &date); +// the volume, or the year in a "Series (Year)" name +QString cblEffectiveVolume(const CblLibraryMetadata &metadata); +// only the case and leading zeros do not count +bool cblSameNumber(const QString &a, const QString &b); + +#endif diff --git a/YACReaderLibrary/cbl/cbl_reader.cpp b/YACReaderLibrary/cbl/cbl_reader.cpp new file mode 100644 index 000000000..41402ab11 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_reader.cpp @@ -0,0 +1,312 @@ +#include "cbl_reader.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +std::optional toInt(const QString &text) +{ + bool ok = false; + const auto value = text.trimmed().toInt(&ok); + return ok ? std::optional(value) : std::nullopt; +} + +// some generators escape text twice, so a value can hold "Infinity, Inc." or "Batman & Robin" +QString decodeReferences(const QString &text) +{ + static const QRegularExpression reference(R"(&(#[0-9]+|#[xX][0-9a-fA-F]+|amp|lt|gt|quot|apos);)"); + if (!text.contains('&')) + return text; + + QString result; + qsizetype last = 0; + auto matches = reference.globalMatch(text); + while (matches.hasNext()) { + const auto match = matches.next(); + const auto name = match.captured(1); + char32_t character = 0; + if (name.startsWith(QLatin1StringView("#x"), Qt::CaseInsensitive)) + character = name.mid(2).toUInt(nullptr, 16); + else if (name.startsWith('#')) + character = name.mid(1).toUInt(); + else + character = name == QLatin1StringView("amp") ? '&' : name == QLatin1StringView("lt") ? '<' + : name == QLatin1StringView("gt") ? '>' + : name == QLatin1StringView("quot") ? '"' + : '\''; + if (character == 0 || character > 0x10FFFF) + continue; + result += text.mid(last, match.capturedStart() - last); + result += QString::fromUcs4(&character, 1); + last = match.capturedEnd(); + } + result += text.mid(last); + return result; +} + +// XML + +QString attribute(const QXmlStreamAttributes &attributes, QLatin1StringView name) +{ + return decodeReferences(attributes.value(name).toString().trimmed()); +} + +QString elementText(QXmlStreamReader &xml) +{ + return decodeReferences(xml.readElementText().trimmed()); +} + +CblBook readXmlBook(QXmlStreamReader &xml) +{ + const auto attributes = xml.attributes(); + CblBook book; + book.series = attribute(attributes, QLatin1StringView("Series")); + book.number = attribute(attributes, QLatin1StringView("Number")); + book.volume = attribute(attributes, QLatin1StringView("Volume")); + book.year = attribute(attributes, QLatin1StringView("Year")); + book.format = attribute(attributes, QLatin1StringView("Format")); + + while (xml.readNextStartElement()) { + if (xml.name() == QLatin1StringView("Database")) { + const auto databaseAttributes = xml.attributes(); + book.databases.append({ attribute(databaseAttributes, QLatin1StringView("Name")), + attribute(databaseAttributes, QLatin1StringView("Series")), + attribute(databaseAttributes, QLatin1StringView("Issue")) }); + xml.skipCurrentElement(); + } else if (xml.name() == QLatin1StringView("Id")) { + book.id = elementText(xml); + } else if (xml.name() == QLatin1StringView("FileName")) { + book.fileName = elementText(xml); + } else { + xml.skipCurrentElement(); + } + } + return book; +} + +// the text of an XML document, in the encoding of its byte order mark or declaration, else UTF-8 +QString xmlText(const QByteArray &data) +{ + if (data.startsWith("\xFF\xFE") || data.startsWith("\xFE\xFF")) + return QStringDecoder(QStringConverter::Utf16)(data); + + static const QRegularExpression declaration(R"(^\s*<\?xml[^>]*encoding\s*=\s*["']([^"']+)["'])"); + const auto match = declaration.match(QString::fromLatin1(data.left(200))); + if (match.hasMatch()) { + QStringDecoder decoder(match.captured(1).toLatin1().constData()); + if (decoder.isValid()) + return decoder(data); + } + return QString::fromUtf8(data); +} + +CblReadResult readXml(const QString &text) +{ + QXmlStreamReader xml(text); + CblReadingList list; + list.format = CblReadingList::Xml; + + if (xml.readNextStartElement() && xml.name() != QLatin1StringView("ReadingList")) + return CblReadError { CblReadError::NotAReadingList, xml.name().toString(), xml.lineNumber(), xml.columnNumber() }; + + while (xml.readNextStartElement()) { + const auto name = xml.name(); + if (name == QLatin1StringView("Name")) { + list.name = elementText(xml); + } else if (name == QLatin1StringView("Summary")) { + list.description = elementText(xml); + } else if (name == QLatin1StringView("NumIssues")) { + list.sourceCount = toInt(xml.readElementText()); + } else if (name == QLatin1StringView("StartYear")) { + list.startYear = toInt(xml.readElementText()); + } else if (name == QLatin1StringView("StartMonth")) { + list.startMonth = toInt(xml.readElementText()); + } else if (name == QLatin1StringView("EndYear")) { + list.endYear = toInt(xml.readElementText()); + } else if (name == QLatin1StringView("EndMonth")) { + list.endMonth = toInt(xml.readElementText()); + } else if (name == QLatin1StringView("Books")) { + // the text from the end of the previous element to the end of a book is that book + auto start = xml.characterOffset(); + while (xml.readNextStartElement()) { + if (xml.name() == QLatin1StringView("Book")) { + auto book = readXmlBook(xml); + book.source = text.mid(start, xml.characterOffset() - start).trimmed(); + list.books.append(book); + } else { + xml.skipCurrentElement(); + } + start = xml.characterOffset(); + } + } else { + xml.skipCurrentElement(); + } + } + + // reading on to the end finds errors after the reading list too + while (!xml.atEnd() && !xml.hasError()) + xml.readNext(); + + if (xml.hasError()) + return CblReadError { CblReadError::Malformed, xml.errorString(), xml.lineNumber(), xml.columnNumber() }; + + return list; +} + +// JSON + +// strings as written; whole numbers without a fraction (seriesStartYear is a number) +QString text(const QJsonValue &value) +{ + if (value.isString()) + return decodeReferences(value.toString().trimmed()); + if (value.isDouble()) { + const auto number = value.toDouble(); + return number == std::floor(number) ? QString::number(static_cast(number)) : QString::number(number); + } + return { }; +} + +std::optional integer(const QJsonValue &value) +{ + if (value.isDouble()) { + const auto number = value.toDouble(); + return number == std::floor(number) ? std::optional(static_cast(number)) : std::nullopt; + } + if (value.isString()) + return toInt(value.toString()); + return std::nullopt; +} + +QStringList texts(const QJsonValue &value) +{ + QStringList result; + for (const auto &item : value.toArray()) { + if (const auto itemText = text(item); !itemText.isEmpty()) + result.append(itemText); + } + return result; +} + +CblBook readJsonBook(const QJsonObject &issue) +{ + CblBook book; + book.series = text(issue.value(QLatin1StringView("seriesName"))); + book.number = text(issue.value(QLatin1StringView("issueNumber"))); + book.volume = text(issue.value(QLatin1StringView("seriesStartYear"))); + book.coverDate = text(issue.value(QLatin1StringView("issueCoverDate"))); + book.year = book.coverDate.section('-', 0, 0); + book.issueType = text(issue.value(QLatin1StringView("issueType"))); + for (const auto &id : issue.value(QLatin1StringView("id")).toArray()) { + const auto ids = id.toObject(); + book.databases.append({ text(ids.value(QLatin1StringView("name"))), + text(ids.value(QLatin1StringView("series"))), + text(ids.value(QLatin1StringView("issue"))) }); + } + return book; +} + +CblReadResult readJson(const QByteArray &data) +{ + QJsonParseError parseError; + const auto document = QJsonDocument::fromJson(data, &parseError); + if (parseError.error != QJsonParseError::NoError) { + const auto before = data.left(parseError.offset); + const auto line = before.count('\n') + 1; + const auto column = parseError.offset - (before.lastIndexOf('\n') + 1) + 1; + return CblReadError { CblReadError::Malformed, parseError.errorString(), line, column }; + } + + const auto root = document.object(); + if (!document.isObject() || !root.value(QLatin1StringView("issueList")).isArray()) + return CblReadError { CblReadError::NotAReadingList, QStringLiteral("no issueList") }; + + CblReadingList list; + list.format = CblReadingList::Json; + list.uuid = text(root.value(QLatin1StringView("fileDetails")).toObject().value(QLatin1StringView("UUID"))); + list.notes = text(root.value(QLatin1StringView("notes"))); + + const auto details = root.value(QLatin1StringView("listDetails")).toObject(); + list.name = text(details.value(QLatin1StringView("name"))); + list.description = text(details.value(QLatin1StringView("description"))); + list.startYear = integer(details.value(QLatin1StringView("startYear"))); + list.endYear = integer(details.value(QLatin1StringView("endYear"))); + list.publisher = text(details.value(QLatin1StringView("publisher"))); + list.imprint = text(details.value(QLatin1StringView("imprint"))); + list.type = text(details.value(QLatin1StringView("type"))); + list.tags = texts(details.value(QLatin1StringView("tags"))); + list.coverImageUrls = texts(details.value(QLatin1StringView("coverImageURLs"))); + for (const auto &item : details.value(QLatin1StringView("relationships")).toArray()) { + const auto relationship = item.toObject(); + list.relationships.append({ text(relationship.value(QLatin1StringView("name"))), + text(relationship.value(QLatin1StringView("UUID"))), + text(relationship.value(QLatin1StringView("relationship"))) }); + } + for (const auto &item : details.value(QLatin1StringView("source")).toArray()) { + const auto source = item.toObject(); + list.sources.append({ text(source.value(QLatin1StringView("name"))), + text(source.value(QLatin1StringView("url"))), + text(source.value(QLatin1StringView("id"))) }); + } + + const auto issues = root.value(QLatin1StringView("issueList")).toArray(); + for (qsizetype i = 0; i < issues.size(); i++) { + if (!issues.at(i).isObject()) + return CblReadError { CblReadError::Malformed, QStringLiteral("issueList item %1 is not an object").arg(i) }; + auto book = readJsonBook(issues.at(i).toObject()); + book.source = QString::fromUtf8(QJsonDocument(issues.at(i).toObject()).toJson(QJsonDocument::Compact)); + list.books.append(book); + } + + return list; +} + +} + +CblReadResult readCbl(QIODevice &device) +{ + auto data = device.readAll(); + if (data.startsWith("\xEF\xBB\xBF")) + data.remove(0, 3); + + const auto first = std::find_if(data.cbegin(), data.cend(), [](char c) { return !QChar::isSpace(uchar(c)); }); + if (first != data.cend() && *first == '{') + return readJson(data); + // the reader gets text, so the offsets of a book are positions in it + return readXml(xmlText(data)); +} + +CblReadResult readCbl(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return CblReadError { CblReadError::CannotOpen, file.errorString() }; + + return readCbl(file); +} + +std::optional readCblBook(const QString &source, const QString &format) +{ + CblReadResult result = CblReadError { CblReadError::NotAReadingList, format }; + if (format == QLatin1StringView("xml")) + // ComicRack writes these namespaces on the root, and a book can use them + result = readXml(QStringLiteral("") + + source + QStringLiteral("")); + else if (format == QLatin1StringView("json")) + result = readJson(QByteArrayLiteral("{\"issueList\":[") + source.toUtf8() + QByteArrayLiteral("]}")); + + const auto *list = std::get_if(&result); + if (list == nullptr || list->books.size() != 1) + return std::nullopt; + auto book = list->books.constFirst(); + book.source = source; + return book; +} diff --git a/YACReaderLibrary/cbl/cbl_reader.h b/YACReaderLibrary/cbl/cbl_reader.h new file mode 100644 index 000000000..c8d711ab7 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_reader.h @@ -0,0 +1,121 @@ +#ifndef CBL_READER_H +#define CBL_READER_H + +#include +#include +#include + +#include +#include + +class QIODevice; + +// CBL reading lists come as ComicRack XML (.cbl, no specification) or as JSON (json-cbl-standard 1.0); +// both give the same model, and fields a format does not have stay empty + +// the ids of a book in one provider database, as the file names it ("cv", "comicvine", "metron", "gcd"…) +struct CblDatabaseIds { + QString name; + QString seriesId; + QString issueId; + + friend bool operator==(const CblDatabaseIds &, const CblDatabaseIds &) = default; +}; + +// every value is the source text: numbers like "-1", "23.2", "½", or "1.MU", and years like "0", stay as they are +struct CblBook { + QString series; + QString number; + // XML Volume; JSON seriesStartYear + QString volume; + // XML Year; JSON the year of issueCoverDate + QString year; + // JSON issueCoverDate, as written (YYYY-MM-DD) + QString coverDate; + // XML Format + QString format; + // JSON issueType + QString issueType; + // ComicRack's Id and FileName + QString id; + QString fileName; + QList databases; + // the book as in the file: the element, or the JSON issue object (written again, with sorted keys) + QString source; + + friend bool operator==(const CblBook &, const CblBook &) = default; +}; + +// a link to another reading list file, by its UUID +struct CblRelationship { + QString name; + QString uuid; + // "previous" or "following" + QString relationship; + + friend bool operator==(const CblRelationship &, const CblRelationship &) = default; +}; + +struct CblSource { + QString name; + QString url; + QString id; + + friend bool operator==(const CblSource &, const CblSource &) = default; +}; + +struct CblReadingList { + enum Format { + Xml, + Json, + }; + + Format format = Xml; + // JSON fileDetails.UUID; the same list file keeps it across versions + QString uuid; + QString name; + // XML Summary; JSON description + QString description; + // XML NumIssues; it can differ from the number of books + std::optional sourceCount; + std::optional startYear; + std::optional startMonth; + std::optional endYear; + std::optional endMonth; + QString publisher; + QString imprint; + // "master", "universal", "team", "character", "story"… + QString type; + QStringList tags; + QStringList coverImageUrls; + QList relationships; + QList sources; + QString notes; + QList books; + + friend bool operator==(const CblReadingList &, const CblReadingList &) = default; +}; + +struct CblReadError { + enum Kind { + CannotOpen, + Malformed, + NotAReadingList, + }; + + Kind kind; + // the reader's message, for logs; the UI shows a text for the kind + QString detail; + qint64 line = 0; + qint64 column = 0; +}; + +using CblReadResult = std::variant; + +// the format is found from the content: '{' starts JSON, anything else is read as XML +CblReadResult readCbl(QIODevice &device); +CblReadResult readCbl(const QString &filePath); +// one book from its source, as an entry keeps it (`format` is "xml" or "json"); nothing when it cannot be read +std::optional readCblBook(const QString &source, const QString &format); + +#endif diff --git a/YACReaderLibrary/cbl/cbl_writer.cpp b/YACReaderLibrary/cbl/cbl_writer.cpp new file mode 100644 index 000000000..a8f4bf32d --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_writer.cpp @@ -0,0 +1,119 @@ +#include "cbl_writer.h" + +#include +#include +#include + +#include +#include + +namespace { + +bool isProvider(const QString &name, std::initializer_list aliases) +{ + return std::any_of(aliases.begin(), aliases.end(), [&](QLatin1StringView alias) { return name.compare(alias, Qt::CaseInsensitive) == 0; }); +} + +// the names of json-cbl-standard 1.0; empty for a provider it has no name for +QString jsonProviderName(const QString &name) +{ + if (isProvider(name, { QLatin1StringView("cv"), QLatin1StringView("comicvine") })) + return QStringLiteral("comicvine"); + if (isProvider(name, { QLatin1StringView("gcd"), QLatin1StringView("grandcomicsdatabase") })) + return QStringLiteral("grandComicsDatabase"); + if (isProvider(name, { QLatin1StringView("metron") })) + return QStringLiteral("metron"); + return { }; +} + +// ComicRack's name for Comic Vine +QString xmlProviderName(const QString &name) +{ + return isProvider(name, { QLatin1StringView("cv"), QLatin1StringView("comicvine") }) ? QStringLiteral("cv") : name; +} + +QString attribute(const char *name, const QString &value) +{ + return value.isEmpty() ? QString() : QStringLiteral(" %1=\"%2\"").arg(QLatin1StringView(name), value.toHtmlEscaped()); +} + +QString xmlBook(const CblBook &book) +{ + const auto year = book.year.isEmpty() ? book.coverDate.section('-', 0, 0) : book.year; + QString children; + if (!book.id.isEmpty()) + children += QStringLiteral(" %1\n").arg(book.id.toHtmlEscaped()); + for (const auto &ids : book.databases) + children += QStringLiteral(" \n").arg(attribute("Name", xmlProviderName(ids.name)), attribute("Series", ids.seriesId), attribute("Issue", ids.issueId)); + if (!book.fileName.isEmpty()) + children += QStringLiteral(" %1\n").arg(book.fileName.toHtmlEscaped()); + + const auto start = QStringLiteral(" \n") : start + QStringLiteral(">\n") + children + QStringLiteral(" \n"); +} + +QByteArray writeXml(const QString &name, const QList &books) +{ + QString text = QStringLiteral("\n" + "\n"); + text += QStringLiteral(" %1\n").arg(name.toHtmlEscaped()); + text += QStringLiteral(" %1\n").arg(books.size()); + text += QStringLiteral(" \n"); + for (const auto &book : books) { + if (book.sourceFormat == QLatin1StringView("xml") && !book.source.isEmpty()) + text += QStringLiteral(" ") + book.source.trimmed() + QStringLiteral("\n"); + else + text += xmlBook(book.book); + } + text += QStringLiteral(" \n\n"); + return text.toUtf8(); +} + +QJsonObject jsonBook(const CblBook &book) +{ + QJsonObject issue; + if (!book.series.isEmpty()) + issue.insert(QStringLiteral("seriesName"), book.series); + bool isYear = false; + if (const auto startYear = book.volume.toInt(&isYear); isYear && startYear >= 1900 && startYear <= 2100) + issue.insert(QStringLiteral("seriesStartYear"), startYear); + if (!book.number.isEmpty()) + issue.insert(QStringLiteral("issueNumber"), book.number); + if (!book.coverDate.isEmpty()) + issue.insert(QStringLiteral("issueCoverDate"), book.coverDate); + if (!book.issueType.isEmpty()) + issue.insert(QStringLiteral("issueType"), book.issueType); + + QJsonArray ids; + for (const auto &databaseIds : book.databases) { + if (const auto provider = jsonProviderName(databaseIds.name); !provider.isEmpty()) + ids.append(QJsonObject { { QStringLiteral("name"), provider }, { QStringLiteral("series"), databaseIds.seriesId }, { QStringLiteral("issue"), databaseIds.issueId } }); + } + if (!ids.isEmpty()) + issue.insert(QStringLiteral("id"), ids); + return issue; +} + +QByteArray writeJson(const QString &name, const QList &books, const QString &uuid) +{ + QJsonArray issues; + for (const auto &book : books) { + const auto source = book.sourceFormat == QLatin1StringView("json") ? QJsonDocument::fromJson(book.source.toUtf8()) : QJsonDocument(); + issues.append(source.isObject() ? source.object() : jsonBook(book.book)); + } + + const QJsonObject root { + { QStringLiteral("fileDetails"), QJsonObject { { QStringLiteral("version"), 1.0 }, { QStringLiteral("UUID"), uuid } } }, + { QStringLiteral("listDetails"), QJsonObject { { QStringLiteral("name"), name } } }, + { QStringLiteral("issueList"), issues }, + }; + return QJsonDocument(root).toJson(QJsonDocument::Indented); +} + +} + +QByteArray writeCbl(const QString &name, const QList &books, CblReadingList::Format format, const QString &uuid) +{ + return format == CblReadingList::Json ? writeJson(name, books, uuid) : writeXml(name, books); +} diff --git a/YACReaderLibrary/cbl/cbl_writer.h b/YACReaderLibrary/cbl/cbl_writer.h new file mode 100644 index 000000000..c07b6ab62 --- /dev/null +++ b/YACReaderLibrary/cbl/cbl_writer.h @@ -0,0 +1,23 @@ +#ifndef CBL_WRITER_H +#define CBL_WRITER_H + +#include "cbl_reader.h" + +#include +#include +#include + +// a book to write, and the source it was imported from +struct CblExportBook { + CblBook book; + // the book as in its file, written as it is when the format is the same; empty when it was not imported + QString source; + // "xml" or "json" + QString sourceFormat; +}; + +// the file of a reading list; a JSON file gets `uuid` as its file UUID. Values the library does not know are left out, +// also when the JSON schema requires them. +QByteArray writeCbl(const QString &name, const QList &books, CblReadingList::Format format, const QString &uuid); + +#endif diff --git a/YACReaderLibrary/classic_comics_view.cpp b/YACReaderLibrary/classic_comics_view.cpp index 98cef21da..92fe83011 100644 --- a/YACReaderLibrary/classic_comics_view.cpp +++ b/YACReaderLibrary/classic_comics_view.cpp @@ -66,13 +66,18 @@ ClassicComicsView::ClassicComicsView(QWidget *parent) connect(comicFlow, &ComicFlowWidget::centerIndexChanged, this, &ClassicComicsView::updateTableView); connect(tableView, &YACReaderTableView::comicRated, this, &ComicsView::comicRated); connect(tableView, &YACReaderTableView::customCoverDropped, this, [this](const QString &imagePath, const QModelIndex &index) { - emit customComicCoverRequested(index.data(ComicModel::IdRole).toULongLong(), imagePath); + if (model && index.isValid()) + emit customComicCoverRequested(model->rowId(index), imagePath); }); connect(comicFlow, &ComicFlowWidget::customCoverDropped, this, [this](const QString &imagePath, int index) { if (model && index >= 0 && index < model->rowCount()) - emit customComicCoverRequested(model->index(index, 0).data(ComicModel::IdRole).toULongLong(), imagePath); + emit customComicCoverRequested(model->rowId(model->index(index, 0)), imagePath); }); connect(comicFlow, &ComicFlowWidget::selected, this, &ComicsView::selected); + connect(tableView->horizontalHeader(), &QHeaderView::sectionMoved, this, [this](int logicalIndex, int, int newVisualIndex) { + if (logicalIndex == ComicModel::State || newVisualIndex == 0) + updateStateColumn(); + }); connect(tableView->horizontalHeader(), &QHeaderView::sectionMoved, this, &ClassicComicsView::saveTableHeadersStatus); connect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &ClassicComicsView::saveTableHeadersStatus); connect(comicFlow, &QWidget::customContextMenuRequested, this, &ClassicComicsView::requestedViewContextMenu); @@ -186,11 +191,13 @@ void ClassicComicsView::setModel(ComicModel *model) tableView->horizontalHeader()->blockSignals(true); tableView->setModel(model); + connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ComicsView::comicSelectionChanged); + connect(tableView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ComicsView::comicSelectionChanged); if (model->rowCount() > 0) tableView->setCurrentIndex(model->index(0, 0)); QStringList paths = model->getPaths(model->getCurrentPath()); // TODO ComicsView: get currentpath from somewhere currentPath()); - comicFlow->setImagePaths(paths); + comicFlow->setImagePaths(paths, model->getUnavailableList()); comicFlow->setMarks(model->getReadList()); bool loadDefaults = true; @@ -224,7 +231,7 @@ void ClassicComicsView::setModel(ComicModel *model) // make sure that columns without title are hidden for (int i = 0; i < tableView->horizontalHeader()->count(); i++) { auto title = tableView->model()->headerData(i, Qt::Horizontal).toString(); - if (title.isEmpty()) { + if (title.isEmpty() && i != ComicModel::State) { tableView->horizontalHeader()->hideSection(i); } } @@ -239,10 +246,42 @@ void ClassicComicsView::setModel(ComicModel *model) } } + updateStateColumn(); + connect(model, &QAbstractItemModel::modelReset, this, &ClassicComicsView::updateStateColumn, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsInserted, this, &ClassicComicsView::scheduleStateColumnUpdate, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsRemoved, this, &ClassicComicsView::scheduleStateColumnUpdate, Qt::UniqueConnection); + // a reload changes a row in place when its comic is deleted or found again + connect(model, &QAbstractItemModel::dataChanged, this, &ClassicComicsView::scheduleStateColumnUpdate, Qt::UniqueConnection); + tableView->horizontalHeader()->blockSignals(false); } } +// a saved layout from before the state column existed has it at the end +void ClassicComicsView::updateStateColumn() +{ + auto *header = tableView->horizontalHeader(); + if (header->count() <= ComicModel::State) + return; + + const bool blocked = header->blockSignals(true); + header->moveSection(header->visualIndex(ComicModel::State), 0); + header->setSectionResizeMode(ComicModel::State, QHeaderView::Fixed); + header->resizeSection(ComicModel::State, 20); + header->setSectionHidden(ComicModel::State, model == nullptr || !model->hasUnavailableRows()); + header->blockSignals(blocked); +} + +void ClassicComicsView::scheduleStateColumnUpdate() +{ + if (stateColumnUpdateScheduled) + return; + stateColumnUpdateScheduled = true; + QMetaObject::invokeMethod(this, [this] { + stateColumnUpdateScheduled = false; + updateStateColumn(); }, Qt::QueuedConnection); +} + void ClassicComicsView::setCurrentIndex(const QModelIndex &index) { tableView->setCurrentIndex(index); @@ -272,7 +311,7 @@ ContentViewState ClassicComicsView::captureViewState() const const auto topIndex = tableView->indexAt(QPoint(0, 0)); if (topIndex.isValid()) { state.topItem.kind = ContentItemRef::Comic; - state.topItem.id = topIndex.data(ComicModel::IdRole).toULongLong(); + state.topItem.comic = model->rowId(topIndex); state.fallbackComicRow = topIndex.row(); state.offset = -tableView->visualRect(topIndex).top(); state.itemExtent = tableView->rowHeight(topIndex.row()); @@ -281,7 +320,7 @@ ContentViewState ClassicComicsView::captureViewState() const const auto selectedIndex = tableView->currentIndex(); if (selectedIndex.isValid()) { state.currentItem.kind = ContentItemRef::Comic; - state.currentItem.id = selectedIndex.data(ComicModel::IdRole).toULongLong(); + state.currentItem.comic = model->rowId(selectedIndex); } return state; } @@ -292,14 +331,14 @@ void ClassicComicsView::restoreViewState(const ContentViewState &state) return; if (state.currentItem.kind == ContentItemRef::Comic) { - const auto current = model->getIndexFromId(state.currentItem.id); + const auto current = model->indexOf(state.currentItem.comic); if (current.isValid()) { tableView->setCurrentIndex(current); comicFlow->setCenterIndexWithoutAnimation(current.row()); } } - const auto topIndex = state.topItem.kind == ContentItemRef::Comic ? model->getIndexFromId(state.topItem.id) : QModelIndex(); + const auto topIndex = state.topItem.kind == ContentItemRef::Comic ? model->indexOf(state.topItem.comic) : QModelIndex(); const auto fallbackRow = qBound(0, state.fallbackComicRow, model->rowCount() - 1); const auto restoreIndex = topIndex.isValid() ? topIndex : model->index(fallbackRow, 0); if (restoreIndex.isValid()) { @@ -466,13 +505,20 @@ void ClassicComicsView::applyModelChanges(const QModelIndex &topLeft, const QMod const auto centerIndex = comicFlow->centerIndex(); for (auto row = topLeft.row(); row <= bottomRight.row(); ++row) { comicFlow->remove(row); - comicFlow->add(model->index(row, 0).data(ComicModel::CoverPathRole).toUrl().toLocalFile(), row); + comicFlow->add(flowCoverPath(model->index(row, 0)), row, !model->index(row, 0).data(ComicModel::AvailableRole).toBool()); } comicFlow->setMarks(model->getReadList()); comicFlow->setCenterIndexWithoutAnimation(centerIndex); } } +// the flow loads files and resources, not URLs +QString ClassicComicsView::flowCoverPath(const QModelIndex &index) +{ + const auto url = index.data(ComicModel::CoverPathRole).toUrl(); + return url.scheme() == QStringLiteral("qrc") ? QStringLiteral(":") + url.path() : url.toLocalFile(); +} + void ClassicComicsView::removeItemsFromFlow(const QModelIndex &parent, int from, int to) { Q_UNUSED(parent); @@ -484,8 +530,7 @@ void ClassicComicsView::addItemsToFlow(const QModelIndex &parent, int from, int { Q_UNUSED(parent); for (int i = from; i <= to; i++) { - auto coverPath = model->index(i, 0).data(ComicModel::CoverPathRole).toUrl().toLocalFile(); - comicFlow->add(coverPath, i); + comicFlow->add(flowCoverPath(model->index(i, 0)), i, !model->index(i, 0).data(ComicModel::AvailableRole).toBool()); } } diff --git a/YACReaderLibrary/classic_comics_view.h b/YACReaderLibrary/classic_comics_view.h index 729c1f0d6..d10a25af8 100644 --- a/YACReaderLibrary/classic_comics_view.h +++ b/YACReaderLibrary/classic_comics_view.h @@ -49,6 +49,8 @@ public slots: void centerComicFlow(const QModelIndex &mi); void updateTableView(int i); void saveTableHeadersStatus(); + void updateStateColumn(); + void scheduleStateColumnUpdate(); void saveSplitterStatus(); void applyModelChanges(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); void removeItemsFromFlow(const QModelIndex &parent, int from, int to); @@ -65,6 +67,7 @@ protected slots: void requestedHeaderContextMenu(const QPoint &point); private: + bool stateColumnUpdateScheduled = false; YACReaderTableView *tableView; QToolBar *toolbar; QWidget *comics; @@ -85,6 +88,7 @@ protected slots: void showSearchingIcon(); void hideSearchingIcon(); void updateSearchingIconPosition(); + static QString flowCoverPath(const QModelIndex &index); QMutex settingsMutex; }; diff --git a/YACReaderLibrary/comic_flow_widget.cpp b/YACReaderLibrary/comic_flow_widget.cpp index 62c9f0013..641c6ff41 100644 --- a/YACReaderLibrary/comic_flow_widget.cpp +++ b/YACReaderLibrary/comic_flow_widget.cpp @@ -116,9 +116,9 @@ void ComicFlowWidget::clear() flow->clear(); } -void ComicFlowWidget::setImagePaths(QStringList paths) +void ComicFlowWidget::setImagePaths(QStringList paths, QList grayscale) { - flow->setImagePaths(paths); + flow->setImagePaths(paths, grayscale); } void ComicFlowWidget::setCenterIndex(int index) @@ -328,9 +328,9 @@ void ComicFlowWidget::updateConfig(QSettings *settings) flow->setMaxAngle(settings->value(MAX_ANGLE).toInt()); } -void ComicFlowWidget::add(const QString &path, int index) +void ComicFlowWidget::add(const QString &path, int index, bool grayscale) { - flow->add(path, index); + flow->add(path, index, grayscale); } void ComicFlowWidget::remove(int cover) diff --git a/YACReaderLibrary/comic_flow_widget.h b/YACReaderLibrary/comic_flow_widget.h index 14b20743d..002363ad6 100644 --- a/YACReaderLibrary/comic_flow_widget.h +++ b/YACReaderLibrary/comic_flow_widget.h @@ -24,7 +24,7 @@ public slots: void unmarkSlide(int index); void setSlideSize(QSize size); void clear(); - void setImagePaths(QStringList paths); + void setImagePaths(QStringList paths, QList grayscale = { }); void setCenterIndex(int index); void setCenterIndexWithoutAnimation(int index); void showSlide(int index); @@ -33,7 +33,7 @@ public slots: void setFlowType(YACReader::FlowType flowType); void render(); void updateConfig(QSettings *settings); - void add(const QString &path, int index); + void add(const QString &path, int index, bool grayscale = false); void remove(int cover); void resortCovers(QList newOrder); diff --git a/YACReaderLibrary/comic_info_object.cpp b/YACReaderLibrary/comic_info_object.cpp new file mode 100644 index 000000000..6f7769a4e --- /dev/null +++ b/YACReaderLibrary/comic_info_object.cpp @@ -0,0 +1,552 @@ +#include "comic_info_object.h" + +#include "overloaded.h" + +#include + +ComicInfoObject::ComicInfoObject(QObject *parent) + : QObject(parent), favorite(false) +{ +} + +ComicInfoObject::ComicInfoObject(const ComicRecord &record, bool isFavorite, QObject *parent) + : QObject(parent), record(record), fileName(recordFileName(record)), isAvailable(recordComic(record) != nullptr), favorite(isFavorite) +{ +} + +qulonglong ComicInfoObject::id() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->id.value(); +} + +bool ComicInfoObject::read() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->read; +} + +bool ComicInfoObject::edited() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->edited; +} + +QString ComicInfoObject::hash() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->hash; +} + +int ComicInfoObject::rating() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->rating; +} + +bool ComicInfoObject::hasBeenOpened() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->hasBeenOpened; +} + +int ComicInfoObject::currentPage() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->currentPage; +} + +int ComicInfoObject::bookmark1() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->bookmark1; +} + +int ComicInfoObject::bookmark2() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->bookmark2; +} + +int ComicInfoObject::bookmark3() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->bookmark3; +} + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +int ComicInfoObject::brightness() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->brightness; +} + +int ComicInfoObject::contrast() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->contrast; +} + +int ComicInfoObject::gamma() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->gamma; +} +QT_WARNING_POP + +QVariant ComicInfoObject::title() const +{ + return recordMetadata(record).title; +} + +QVariant ComicInfoObject::coverPage() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->coverPage; +} + +QVariant ComicInfoObject::numPages() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->numPages; +} + +QVariant ComicInfoObject::number() const +{ + return recordMetadata(record).number; +} + +QVariant ComicInfoObject::isBis() const +{ + return recordMetadata(record).isBis; +} + +QVariant ComicInfoObject::count() const +{ + return recordMetadata(record).count; +} + +QVariant ComicInfoObject::volume() const +{ + return recordMetadata(record).volume; +} + +QVariant ComicInfoObject::storyArc() const +{ + return recordMetadata(record).storyArc; +} + +QVariant ComicInfoObject::arcNumber() const +{ + return recordMetadata(record).arcNumber; +} + +QVariant ComicInfoObject::arcCount() const +{ + return recordMetadata(record).arcCount; +} + +QVariant ComicInfoObject::genere() const +{ + return recordMetadata(record).genere; +} + +QVariant ComicInfoObject::writer() const +{ + return recordMetadata(record).writer; +} + +QVariant ComicInfoObject::penciller() const +{ + return recordMetadata(record).penciller; +} + +QVariant ComicInfoObject::inker() const +{ + return recordMetadata(record).inker; +} + +QVariant ComicInfoObject::colorist() const +{ + return recordMetadata(record).colorist; +} + +QVariant ComicInfoObject::letterer() const +{ + return recordMetadata(record).letterer; +} + +QVariant ComicInfoObject::coverArtist() const +{ + return recordMetadata(record).coverArtist; +} + +QVariant ComicInfoObject::date() const +{ + return recordMetadata(record).date; +} + +QVariant ComicInfoObject::publisher() const +{ + return recordMetadata(record).publisher; +} + +QVariant ComicInfoObject::format() const +{ + return recordMetadata(record).format; +} + +QVariant ComicInfoObject::color() const +{ + return recordMetadata(record).color; +} + +QVariant ComicInfoObject::ageRating() const +{ + return recordMetadata(record).ageRating; +} + +QVariant ComicInfoObject::synopsis() const +{ + return recordMetadata(record).synopsis; +} + +QVariant ComicInfoObject::characters() const +{ + return recordMetadata(record).characters; +} + +QVariant ComicInfoObject::notes() const +{ + return recordMetadata(record).notes; +} + +QVariant ComicInfoObject::comicVineID() const +{ + return recordMetadata(record).comicVineID; +} + +QVariant ComicInfoObject::lastTimeOpened() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->lastTimeOpened; +} + +QVariant ComicInfoObject::coverSizeRatio() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->coverSizeRatio; +} + +QVariant ComicInfoObject::originalCoverSize() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->originalCoverSize; +} + +QVariant ComicInfoObject::added() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->added; +} + +QVariant ComicInfoObject::type() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->type; +} + +QVariant ComicInfoObject::editor() const +{ + return recordMetadata(record).editor; +} + +QVariant ComicInfoObject::imprint() const +{ + return recordMetadata(record).imprint; +} + +QVariant ComicInfoObject::teams() const +{ + return recordMetadata(record).teams; +} + +QVariant ComicInfoObject::locations() const +{ + return recordMetadata(record).locations; +} + +QVariant ComicInfoObject::series() const +{ + return recordMetadata(record).series; +} + +QVariant ComicInfoObject::alternateSeries() const +{ + return recordMetadata(record).alternateSeries; +} + +QVariant ComicInfoObject::alternateNumber() const +{ + return recordMetadata(record).alternateNumber; +} + +QVariant ComicInfoObject::alternateCount() const +{ + return recordMetadata(record).alternateCount; +} + +QVariant ComicInfoObject::languageISO() const +{ + return recordMetadata(record).languageISO; +} + +QVariant ComicInfoObject::seriesGroup() const +{ + return recordMetadata(record).seriesGroup; +} + +QVariant ComicInfoObject::mainCharacterOrTeam() const +{ + return recordMetadata(record).mainCharacterOrTeam; +} + +QVariant ComicInfoObject::review() const +{ + return recordMetadata(record).review; +} + +QVariant ComicInfoObject::tags() const +{ + return recordMetadata(record).tags; +} + +QVariant ComicInfoObject::imageFiltersJson() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->imageFiltersJson; +} + +QVariant ComicInfoObject::lastTimeImageFiltersSet() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->lastTimeImageFiltersSet; +} + +QVariant ComicInfoObject::lastTimeCoverSet() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->lastTimeCoverSet; +} + +QVariant ComicInfoObject::usesExternalCover() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->usesExternalCover; +} + +QVariant ComicInfoObject::lastTimeMetadataSet() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->lastTimeMetadataSet; +} + +bool ComicInfoObject::isFavorite() const +{ + return favorite; +} + +void ComicInfoObject::setRead(bool read) +{ + auto *info = recordInfo(record); + if (info != nullptr && read != info->read) { + info->read = read; + emit readChanged(); + } +} + +void ComicInfoObject::setRating(int rating) +{ + auto *info = recordInfo(record); + if (info != nullptr && rating != info->rating) { + info->rating = rating; + emit ratingChanged(); + } +} + +void ComicInfoObject::setFavorite(bool isFavorite) +{ + if (isFavorite != favorite) { + favorite = isFavorite; + emit favoriteChanged(); + } +} + +QStringList ComicInfoObject::getWriters() const +{ + return recordMetadata(record).getWriters(); +} + +QStringList ComicInfoObject::getPencillers() const +{ + return recordMetadata(record).getPencillers(); +} + +QStringList ComicInfoObject::getInkers() const +{ + return recordMetadata(record).getInkers(); +} + +QStringList ComicInfoObject::getColorists() const +{ + return recordMetadata(record).getColorists(); +} + +QStringList ComicInfoObject::getLetterers() const +{ + return recordMetadata(record).getLetterers(); +} + +QStringList ComicInfoObject::getCoverArtists() const +{ + return recordMetadata(record).getCoverArtists(); +} + +QStringList ComicInfoObject::getEditors() const +{ + return recordMetadata(record).getEditors(); +} + +QStringList ComicInfoObject::getImprint() const +{ + return recordMetadata(record).getImprint(); +} + +QStringList ComicInfoObject::getCharacters() const +{ + return recordMetadata(record).getCharacters(); +} + +QStringList ComicInfoObject::getTeams() const +{ + return recordMetadata(record).getTeams(); +} + +QStringList ComicInfoObject::getLocations() const +{ + return recordMetadata(record).getLocations(); +} + +QStringList ComicInfoObject::getTags() const +{ + return recordMetadata(record).getTags(); +} + +QString ComicInfoObject::getTypeString() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + return info->getTypeString(); +} + +QString ComicInfoObject::getStoryArcInfoString() const +{ + return recordMetadata(record).getStoryArcInfoString(); +} + +QString ComicInfoObject::getAlternateSeriesString() const +{ + return recordMetadata(record).getAlternateSeriesString(); +} + +QString ComicInfoObject::getTitleOrFileName() const +{ + return orDash(titleOrFileName(recordMetadata(record), fileName)); +} + +qulonglong ComicInfoObject::getFileSize() const +{ + const auto *info = recordInfo(record); + if (info == nullptr) + return { }; + // the size is encoded in the hash after the SHA-1 + return info->hash.right(info->hash.length() - 40).toLongLong(); +} + +QString ComicInfoObject::getTitleIncludingNumber() const +{ + return orDash(titleIncludingNumber(recordMetadata(record), fileName)); +} + +QString ComicInfoObject::getInfoTitle() const +{ + return orDash(infoTitle(recordMetadata(record), fileName)); +} + +// Display fallback only; never stored in the comic's metadata. +QString ComicInfoObject::orDash(const QString &title) +{ + return title.isEmpty() ? QStringLiteral("-") : title; +} + +bool ComicInfoObject::available() const +{ + return isAvailable; +} + +bool ComicInfoObject::canSetReadState() const +{ + return recordInfo(record) != nullptr; +} diff --git a/YACReaderLibrary/comic_info_object.h b/YACReaderLibrary/comic_info_object.h new file mode 100644 index 000000000..733970353 --- /dev/null +++ b/YACReaderLibrary/comic_info_object.h @@ -0,0 +1,199 @@ +#ifndef COMIC_INFO_OBJECT_H +#define COMIC_INFO_OBJECT_H + +#include "comic_db.h" +#include "comic_record.h" + +#include + +class ComicInfoObject : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qulonglong id READ id CONSTANT) + Q_PROPERTY(bool read READ read WRITE setRead NOTIFY readChanged) + Q_PROPERTY(bool edited READ edited CONSTANT) + Q_PROPERTY(QString hash READ hash CONSTANT) + + Q_PROPERTY(int rating READ rating WRITE setRating NOTIFY ratingChanged) + + Q_PROPERTY(bool hasBeenOpened READ hasBeenOpened CONSTANT) + + Q_PROPERTY(int currentPage READ currentPage CONSTANT) + Q_PROPERTY(int bookmark1 READ bookmark1 CONSTANT) + Q_PROPERTY(int bookmark2 READ bookmark2 CONSTANT) + Q_PROPERTY(int bookmark3 READ bookmark3 CONSTANT) + Q_PROPERTY(int brightness READ brightness CONSTANT) + Q_PROPERTY(int contrast READ contrast CONSTANT) + Q_PROPERTY(int gamma READ gamma CONSTANT) + + Q_PROPERTY(QVariant title READ title CONSTANT) + Q_PROPERTY(QVariant coverPage READ coverPage CONSTANT) + Q_PROPERTY(QVariant numPages READ numPages CONSTANT) + Q_PROPERTY(QVariant number READ number CONSTANT) + Q_PROPERTY(QVariant isBis READ isBis CONSTANT) + Q_PROPERTY(QVariant count READ count CONSTANT) + Q_PROPERTY(QVariant volume READ volume CONSTANT) + Q_PROPERTY(QVariant storyArc READ storyArc CONSTANT) + Q_PROPERTY(QVariant arcNumber READ arcNumber CONSTANT) + Q_PROPERTY(QVariant arcCount READ arcCount CONSTANT) + Q_PROPERTY(QVariant genere READ genere CONSTANT) + Q_PROPERTY(QVariant writer READ writer CONSTANT) + Q_PROPERTY(QVariant penciller READ penciller CONSTANT) + Q_PROPERTY(QVariant inker READ inker CONSTANT) + Q_PROPERTY(QVariant colorist READ colorist CONSTANT) + Q_PROPERTY(QVariant letterer READ letterer CONSTANT) + Q_PROPERTY(QVariant coverArtist READ coverArtist CONSTANT) + Q_PROPERTY(QVariant date READ date CONSTANT) + Q_PROPERTY(QVariant publisher READ publisher CONSTANT) + Q_PROPERTY(QVariant format READ format CONSTANT) + Q_PROPERTY(QVariant color READ color CONSTANT) + Q_PROPERTY(QVariant ageRating READ ageRating CONSTANT) + Q_PROPERTY(QVariant synopsis READ synopsis CONSTANT) + Q_PROPERTY(QVariant characters READ characters CONSTANT) + Q_PROPERTY(QVariant notes READ notes CONSTANT) + Q_PROPERTY(QVariant comicVineID READ comicVineID CONSTANT) + Q_PROPERTY(QVariant lastTimeOpened READ lastTimeOpened CONSTANT) + Q_PROPERTY(QVariant coverSizeRatio READ coverSizeRatio CONSTANT) + Q_PROPERTY(QVariant originalCoverSize READ originalCoverSize CONSTANT) + Q_PROPERTY(QVariant added READ added CONSTANT) + Q_PROPERTY(QVariant type READ type CONSTANT) + Q_PROPERTY(QVariant editor READ editor CONSTANT) + Q_PROPERTY(QVariant imprint READ imprint CONSTANT) + Q_PROPERTY(QVariant teams READ teams CONSTANT) + Q_PROPERTY(QVariant locations READ locations CONSTANT) + Q_PROPERTY(QVariant series READ series CONSTANT) + Q_PROPERTY(QVariant alternateSeries READ alternateSeries CONSTANT) + Q_PROPERTY(QVariant alternateNumber READ alternateNumber CONSTANT) + Q_PROPERTY(QVariant alternateCount READ alternateCount CONSTANT) + Q_PROPERTY(QVariant languageISO READ languageISO CONSTANT) + Q_PROPERTY(QVariant seriesGroup READ seriesGroup CONSTANT) + Q_PROPERTY(QVariant mainCharacterOrTeam READ mainCharacterOrTeam CONSTANT) + Q_PROPERTY(QVariant review READ review CONSTANT) + Q_PROPERTY(QVariant tags READ tags CONSTANT) + Q_PROPERTY(QVariant imageFiltersJson READ imageFiltersJson CONSTANT) + Q_PROPERTY(QVariant lastTimeImageFiltersSet READ lastTimeImageFiltersSet CONSTANT) + Q_PROPERTY(QVariant lastTimeCoverSet READ lastTimeCoverSet CONSTANT) + Q_PROPERTY(QVariant usesExternalCover READ usesExternalCover CONSTANT) + Q_PROPERTY(QVariant lastTimeMetadataSet READ lastTimeMetadataSet CONSTANT) + + Q_PROPERTY(bool isFavorite READ isFavorite WRITE setFavorite NOTIFY favoriteChanged) + // false for a comic whose file is no longer in the library + Q_PROPERTY(bool available READ available CONSTANT) + // false for a placeholder, which has no read state or rating + Q_PROPERTY(bool canSetReadState READ canSetReadState CONSTANT) + +public: + explicit ComicInfoObject(QObject *parent = nullptr); + ComicInfoObject(const ComicRecord &record, bool isFavorite, QObject *parent = nullptr); + + qulonglong id() const; + bool read() const; + bool edited() const; + QString hash() const; + int rating() const; + bool hasBeenOpened() const; + int currentPage() const; + int bookmark1() const; + int bookmark2() const; + int bookmark3() const; + int brightness() const; + int contrast() const; + int gamma() const; + + QVariant title() const; + QVariant coverPage() const; + QVariant numPages() const; + QVariant number() const; + QVariant isBis() const; + QVariant count() const; + QVariant volume() const; + QVariant storyArc() const; + QVariant arcNumber() const; + QVariant arcCount() const; + QVariant genere() const; + QVariant writer() const; + QVariant penciller() const; + QVariant inker() const; + QVariant colorist() const; + QVariant letterer() const; + QVariant coverArtist() const; + QVariant date() const; + QVariant publisher() const; + QVariant format() const; + QVariant color() const; + QVariant ageRating() const; + QVariant synopsis() const; + QVariant characters() const; + QVariant notes() const; + QVariant comicVineID() const; + QVariant lastTimeOpened() const; + QVariant coverSizeRatio() const; + QVariant originalCoverSize() const; + QVariant added() const; + QVariant type() const; + QVariant editor() const; + QVariant imprint() const; + QVariant teams() const; + QVariant locations() const; + QVariant series() const; + QVariant alternateSeries() const; + QVariant alternateNumber() const; + QVariant alternateCount() const; + QVariant languageISO() const; + QVariant seriesGroup() const; + QVariant mainCharacterOrTeam() const; + QVariant review() const; + QVariant tags() const; + QVariant imageFiltersJson() const; + QVariant lastTimeImageFiltersSet() const; + QVariant lastTimeCoverSet() const; + QVariant usesExternalCover() const; + QVariant lastTimeMetadataSet() const; + + bool isFavorite() const; + bool available() const; + bool canSetReadState() const; + + void setRead(bool read); + void setRating(int rating); + void setFavorite(bool isFavorite); + + Q_INVOKABLE QStringList getWriters() const; + Q_INVOKABLE QStringList getPencillers() const; + Q_INVOKABLE QStringList getInkers() const; + Q_INVOKABLE QStringList getColorists() const; + Q_INVOKABLE QStringList getLetterers() const; + Q_INVOKABLE QStringList getCoverArtists() const; + Q_INVOKABLE QStringList getEditors() const; + Q_INVOKABLE QStringList getImprint() const; + Q_INVOKABLE QStringList getCharacters() const; + Q_INVOKABLE QStringList getTeams() const; + Q_INVOKABLE QStringList getLocations() const; + Q_INVOKABLE QStringList getTags() const; + + Q_INVOKABLE QString getTypeString() const; + Q_INVOKABLE QString getStoryArcInfoString() const; + Q_INVOKABLE QString getAlternateSeriesString() const; + + Q_INVOKABLE QString getTitleOrFileName() const; + Q_INVOKABLE qulonglong getFileSize() const; + Q_INVOKABLE QString getTitleIncludingNumber() const; + Q_INVOKABLE QString getInfoTitle() const; + +signals: + void readChanged(); + void ratingChanged(); + void favoriteChanged(); + +private: + static QString orDash(const QString &title); + + ComicRecord record; + // the last title fallback: the file name, or the last known one of a deleted comic + QString fileName; + bool isAvailable = true; + bool favorite; +}; + +#endif diff --git a/YACReaderLibrary/comic_info_repairer.cpp b/YACReaderLibrary/comic_info_repairer.cpp index 8a8052ba5..ead7ceddd 100644 --- a/YACReaderLibrary/comic_info_repairer.cpp +++ b/YACReaderLibrary/comic_info_repairer.cpp @@ -6,6 +6,7 @@ #include "data_base_management.h" #include "db_helper.h" #include "initial_comic_info_extractor.h" +#include "staged_cover.h" #include "xml_info_parser.h" #include "yacreader_global.h" @@ -268,7 +269,7 @@ void prepareInvalidPageCountUpdate(QSqlQuery &query, query.prepare(QString("UPDATE comic_info SET %1 WHERE id = :id").arg(assignments.join(", "))); query.bindValue(":numPages", extracted.numPages); - query.bindValue(":id", info.id); + query.bindValue(":id", info.id.value()); if (preserveCustomCover) { if (!hasValidRatio(info.coverSizeRatio)) { @@ -440,14 +441,21 @@ void ComicInfoRepairer::run() LibraryMaintenanceLock maintenanceLock(source); if (!maintenanceLock.tryLock(removeStaleLock)) { - repairSummary.lockedByAnotherProcess = true; + repairSummary.maintenanceError = maintenanceLock.error(); + repairSummary.lockedByAnotherProcess = maintenanceLock.error() == QLockFile::LockFailedError; repairSummary.lockHolderInfo = maintenanceLock.holderInfo(); - repairSummary.lockHolderIsRunningLocally = maintenanceLock.holderIsRunningLocally(); + repairSummary.lockHolderIsRunningLocally = maintenanceLock.holderIsRunning(); repairSummary.error = maintenanceLock.errorString(); + if (!repairSummary.lockedByAnotherProcess) + emit failed(repairSummary.error); return; } QString backupError; + if (!DataBaseManagement::recoverInterruptedRestore(source, &repairSummary.error, true)) { + emit failed(repairSummary.error); + return; + } if (!DataBaseManagement::backupLibrary(source, DatabaseBackupReason::BeforeRepair, &backupError)) { repairSummary.error = QString("Unable to back up library database: %1").arg(backupError); QLOG_ERROR() << repairSummary.error; @@ -487,6 +495,8 @@ void ComicInfoRepairer::run() while (leftovers.hasNext()) { QFile::remove(leftovers.next()); } + + StagedCover::removeLeftovers(coversPath); } qulonglong afterId = 0; @@ -543,12 +553,13 @@ void ComicInfoRepairer::run() bool repaired = false; if (repairCase == RepairCase::InvalidPageCount) { // this repair needs the full comic_info row: it is the base the parsed XML metadata is merged over - const auto info = DBHelper::loadComicInfo(task.hash, db); + const auto storedInfo = DBHelper::loadComicInfo(task.hash, db); + const auto info = storedInfo.value_or(ComicInfo()); QImage customCover; - const bool preserveCustomCover = info.existOnDb && info.usesExternalCover.toBool() && customCover.load(coverPath); + const bool preserveCustomCover = storedInfo && info.usesExternalCover.toBool() && customCover.load(coverPath); const auto extracted = extractInfo(comicPath, task.coverPage, importXmlMetadata); - if (info.existOnDb && extracted.numPages > 0 && (preserveCustomCover || (extracted.hasValidCover && extracted.originalCoverSize.second > 0))) { + if (storedInfo && extracted.numPages > 0 && (preserveCustomCover || (extracted.hasValidCover && extracted.originalCoverSize.second > 0))) { auto updatedInfo = info; const bool xmlParsed = importXmlMetadata && parseXMLIntoInfo(extracted.xmlData, updatedInfo); QSqlQuery update(db); diff --git a/YACReaderLibrary/comic_info_repairer.h b/YACReaderLibrary/comic_info_repairer.h index a11a574ae..b89dfced3 100644 --- a/YACReaderLibrary/comic_info_repairer.h +++ b/YACReaderLibrary/comic_info_repairer.h @@ -1,6 +1,7 @@ #ifndef COMIC_INFO_REPAIRER_H #define COMIC_INFO_REPAIRER_H +#include #include #include @@ -18,9 +19,10 @@ struct ComicInfoRepairSummary { QString error; QStringList failedFilePaths; QStringList missingFilePaths; - // set when another repair holds the library lock; holder info comes from the + // set on lock contention with another maintenance operation; holder info comes from the // lock file and is empty if it couldn't be read bool lockedByAnotherProcess { false }; + QLockFile::LockError maintenanceError { QLockFile::NoError }; bool lockHolderIsRunningLocally { false }; QString lockFilePath; QString lockHolderInfo; @@ -33,7 +35,7 @@ class ComicInfoRepairer : public QThread public: explicit ComicInfoRepairer(QSettings *settings, QObject *parent = nullptr); - // removeStaleLock removes an existing lock file before locking, for retrying + // removeStaleLock attempts removal only after lock contention, for retrying // after the user confirmed that the previous holder is not running anymore void repairLibrary(const QString &source, const QString &target, bool removeStaleLock = false); ComicInfoRepairSummary summary() const; diff --git a/YACReaderLibrary/comic_management_coordinator.cpp b/YACReaderLibrary/comic_management_coordinator.cpp index 6e1ab5dcd..597c35c03 100644 --- a/YACReaderLibrary/comic_management_coordinator.cpp +++ b/YACReaderLibrary/comic_management_coordinator.cpp @@ -1,16 +1,25 @@ #include "comic_management_coordinator.h" #include "api_key_dialog.h" +#include "cbl_import_dialog.h" +#include "cbl_match_comic_dialog.h" #include "comic_files_manager.h" #include "comic_vine_dialog.h" #include "comics_remover.h" #include "cover_utils.h" +#include "data_base_management.h" #include "db_helper.h" +#include "folder.h" #include "folder_model.h" #include "initial_comic_info_extractor.h" +#include "library_comic_match.h" #include "library_comic_opener.h" +#include "placeholder_cover.h" #include "properties_dialog.h" +#include "reading_list.h" #include "reading_list_model.h" +#include "staged_cover.h" +#include "tracked_issues.h" #include "yacreader_global_gui.h" #include @@ -71,6 +80,7 @@ ComicManagementCoordinator::ComicManagementCoordinator(QWidget *window, : QObject(window), window(window), settings(settings), comicsModel(comicsModel), foldersModel(foldersModel), foldersModelProxy(foldersModelProxy), propertiesDialog(propertiesDialog), comicVineDialog(comicVineDialog), selectionProvider(std::move(selectionProvider)), currentListProvider(std::move(currentListProvider)), currentFolderProvider(std::move(currentFolderProvider)), currentComicProvider(std::move(currentComicProvider)), comicOpeningAllowedProvider(std::move(comicOpeningAllowedProvider)), libraryIdProvider(std::move(libraryIdProvider)), libraryPathProvider(std::move(libraryPathProvider)) { connect(propertiesDialog, &PropertiesDialog::coverChangedSignal, comicsModel, &ComicModel::notifyCoverChange); + connect(propertiesDialog, &PropertiesDialog::placeholderCoverChanged, comicsModel, &ComicModel::notifyPlaceholderCoverChange); connect(propertiesDialog, &QDialog::accepted, this, &ComicManagementCoordinator::currentSourceRefreshAccepted); connect(propertiesDialog, &QDialog::rejected, this, &ComicManagementCoordinator::currentSourceRefreshCancelled); connect(comicVineDialog, &QDialog::accepted, this, &ComicManagementCoordinator::currentSourceRefreshAccepted, Qt::QueuedConnection); @@ -103,12 +113,12 @@ void ComicManagementCoordinator::moveAndImportComicsToFolder(const QListaddComicsToFavorites(selectionProvider()); + comicsModel->addComicsToFavorites(comicsModel->supportedIndexes(selectionProvider(), ComicAction::AddToFavorites)); } void ComicManagementCoordinator::addSelectedComicsToLabel(qulonglong labelId) { - comicsModel->addComicsToLabel(selectionProvider(), labelId); + comicsModel->addComicsToLabel(comicsModel->supportedIndexes(selectionProvider(), ComicAction::AddToList), labelId); } void ComicManagementCoordinator::openCurrentComic() @@ -117,13 +127,24 @@ void ComicManagementCoordinator::openCurrentComic() return; const auto currentComic = currentComicProvider(); - if (!currentComic.isValid()) + if (!comicsModel->supports(currentComic, ComicAction::Open)) return; - openComic(comicsModel->getComic(currentComic), comicsModel->getMode()); + const auto comic = comicsModel->getComic(currentComic); + if (!comic) + return; + + const auto rowId = comicsModel->rowId(currentComic); + const auto entry = std::get_if(&rowId); + startReader(*comic, comicsModel->getMode(), entry != nullptr && entry->listKind == ListKind::ReadingList ? entry->id : 0); } void ComicManagementCoordinator::openComic(const ComicDB &comic, ComicModel::Mode mode) +{ + startReader(comic, mode, 0); +} + +void ComicManagementCoordinator::startReader(const ComicDB &comic, ComicModel::Mode mode, qulonglong entryId) { const auto source = mode == ComicModel::ReadingList ? OpenComicSource::Source::ReadingList @@ -132,7 +153,7 @@ void ComicManagementCoordinator::openComic(const ComicDB &comic, ComicModel::Mod const auto thirdPartyReaderCommand = settings->value(THIRD_PARTY_READER_COMMAND, "").toString(); if (thirdPartyReaderCommand.isEmpty()) { - const auto yacreaderFound = YACReader::openComic(comic, libraryIdProvider(), libraryPath, OpenComicSource { source, comicsModel->getSourceId() }); + const auto yacreaderFound = YACReader::openComic(comic, libraryIdProvider(), libraryPath, OpenComicSource { source, comicsModel->getSourceId(), mode == ComicModel::ReadingList ? entryId : 0 }); if (!yacreaderFound) { #ifdef Q_OS_WIN QMessageBox::critical(window, tr("YACReader not found"), tr("YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary.")); @@ -143,14 +164,14 @@ void ComicManagementCoordinator::openComic(const ComicDB &comic, ComicModel::Mod return; } - if (!YACReader::openComicInThirdPartyApp(thirdPartyReaderCommand, QDir::cleanPath(libraryPath + comic.path))) + if (!YACReader::openComicInThirdPartyApp(thirdPartyReaderCommand, QDir::cleanPath(libraryPath + comic.file.path))) QMessageBox::critical(window, tr("Error"), tr("Error opening comic with third party reader.")); } void ComicManagementCoordinator::openContainingFolderOfCurrentComic() { const auto currentComic = currentComicProvider(); - if (!currentComic.isValid()) + if (!comicsModel->supports(currentComic, ComicAction::OpenContainingFolder)) return; const QFileInfo file(QDir::cleanPath(libraryPathProvider() + comicsModel->getComicPath(currentComic))); @@ -186,9 +207,15 @@ void ComicManagementCoordinator::showComicVineScraper() if (!comicVineSettings.contains(COMIC_VINE_API_KEY)) return; - const auto comics = comicsModel->getComics(selectionProvider()); + QList comics; + for (const auto &index : comicsModel->supportedIndexes(selectionProvider(), ComicAction::SearchComicVine)) + comics.append(comicsModel->record(index)); + if (comics.isEmpty()) + return; + comicVineDialog->databasePath = foldersModel->getDatabase(); comicVineDialog->basePath = libraryPathProvider(); + comicVineDialog->folderId = comicsModel->getMode() == ComicModel::Folder ? std::optional(FolderId(comicsModel->getSourceId())) : std::nullopt; comicVineDialog->setComics(comics); emit currentSourceRefreshStarted(); @@ -229,41 +256,116 @@ void ComicManagementCoordinator::moveAndImportComics(const QListgetComics(indexList); - if (comics.isEmpty()) + const auto indexList = comicsModel->supportedIndexes(selectionProvider(), ComicAction::EditMetadata); + if (indexList.isEmpty()) return; propertiesDialog->databasePath = foldersModel->getDatabase(); propertiesDialog->basePath = libraryPathProvider(); if (indexList.length() > 1) { - propertiesDialog->setComics(comics); + QList records; + for (const auto &index : indexList) + records.append(comicsModel->record(index)); + propertiesDialog->setComics(records); } else { - const auto allComics = comicsModel->getAllComics(); - propertiesDialog->setComicsForSequentialEditing(allComics.indexOf(comics.constFirst()), allComics); + // sequential editing goes through the rows that can be edited, in view order + QList records; + int start = 0; + for (int row = 0; row < comicsModel->rowCount(); row++) { + const auto index = comicsModel->index(row, 0); + if (!comicsModel->supports(index, ComicAction::EditMetadata)) + continue; + if (row == indexList.constFirst().row()) + start = records.size(); + records.append(comicsModel->record(index)); + } + if (!propertiesDialog->setComicsForSequentialEditing(start, records)) + return; } emit currentSourceRefreshStarted(); propertiesDialog->show(); } +std::optional ComicManagementCoordinator::shownFolderId() const +{ + if (comicsModel->getMode() != ComicModel::Folder) + return std::nullopt; + return comicsModel->getSourceId(); +} + +void ComicManagementCoordinator::trackMissingIssues(qulonglong folderId) +{ + const auto dataPath = foldersModel->getDatabase(); + std::optional folder; + std::optional numbers; + DescriptiveComicMetadata run; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(dataPath); + folder = DBHelper::loadFolder(folderId, db); + numbers = folderIssueNumbers(FolderId(folderId), db); + run = folderRunMetadata(FolderId(folderId), db); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (!folder || !numbers) + return; + + propertiesDialog->databasePath = dataPath; + propertiesDialog->basePath = libraryPathProvider(); + propertiesDialog->setTrackMissingIssues(FolderId(folderId), folder->name, run, *numbers); + + emit currentSourceRefreshStarted(); + propertiesDialog->show(); +} + +std::optional ComicManagementCoordinator::shownReadingListId() const +{ + if (comicsModel->getMode() != ComicModel::ReadingList) + return std::nullopt; + return comicsModel->getSourceId(); +} + +void ComicManagementCoordinator::trackMissingIssuesInReadingList(qulonglong readingListId) +{ + const auto dataPath = foldersModel->getDatabase(); + std::optional list; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(dataPath); + list = DBHelper::loadReadingList(ReadingListId(readingListId), db); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (!list) + return; + + propertiesDialog->databasePath = dataPath; + propertiesDialog->basePath = libraryPathProvider(); + propertiesDialog->setTrackMissingIssues(list->id, list->name); + + emit currentSourceRefreshStarted(); + propertiesDialog->show(); +} + void ComicManagementCoordinator::setSelectedComicsRead() { - comicsModel->setComicsRead(selectionProvider(), YACReader::Read); + comicsModel->setComicsRead(comicsModel->supportedIndexes(selectionProvider(), ComicAction::SetReadState), YACReader::Read); emit currentComicViewUpdateRequested(); } void ComicManagementCoordinator::setSelectedComicsUnread() { - comicsModel->setComicsRead(selectionProvider(), YACReader::Unread); + comicsModel->setComicsRead(comicsModel->supportedIndexes(selectionProvider(), ComicAction::SetReadState), YACReader::Unread); emit currentComicViewUpdateRequested(); } void ComicManagementCoordinator::setComicUnread(qulonglong libraryId, const ComicDB &comic) { auto info = comic.info; - info.setRead(false); + info.read = false; info.currentPage = 1; info.hasBeenOpened = false; info.lastTimeOpened = QVariant(); @@ -273,12 +375,12 @@ void ComicManagementCoordinator::setComicUnread(qulonglong libraryId, const Comi void ComicManagementCoordinator::setSelectedComicsType(YACReader::FileType type) { - comicsModel->setComicsType(selectionProvider(), type); + comicsModel->setComicsType(comicsModel->supportedIndexes(selectionProvider(), ComicAction::SetType), type); } void ComicManagementCoordinator::resetSelectedComicRatings() { - const auto indexList = selectionProvider(); + const auto indexList = comicsModel->supportedIndexes(selectionProvider(), ComicAction::SetReadState); comicsModel->startTransaction(); for (const auto &index : indexList) comicsModel->resetComicRating(index); @@ -287,12 +389,12 @@ void ComicManagementCoordinator::resetSelectedComicRatings() void ComicManagementCoordinator::assignNumbers() { - const auto selectedIds = selectedComicIds(); + const auto selectedIds = selectedRowIds(ComicAction::AssignNumbers); if (selectedIds.isEmpty()) return; const auto source = currentSource(); - const auto initialIndexes = indexesForComicIds(selectedIds, source); + const auto initialIndexes = indexesForRows(selectedIds, source); if (initialIndexes.isEmpty()) return; @@ -301,7 +403,7 @@ void ComicManagementCoordinator::assignNumbers() bool accepted; startingNumber = QInputDialog::getInt(window, QCoreApplication::translate("LibraryWindow", "Assign comics numbers"), - QCoreApplication::translate("LibraryWindow", "Assign numbers starting in:"), + QCoreApplication::translate("LibraryWindow", "Assign numbers starting at:"), startingNumber, 0, 2147483647, @@ -311,7 +413,7 @@ void ComicManagementCoordinator::assignNumbers() return; } - const auto indexList = indexesForComicIds(selectedIds, source); + const auto indexList = indexesForRows(selectedIds, source); if (indexList.isEmpty()) return; @@ -320,39 +422,43 @@ void ComicManagementCoordinator::assignNumbers() void ComicManagementCoordinator::deleteMetadataFromSelectedComics() { - auto comics = comicsModel->getComics(selectionProvider()); - if (comics.isEmpty()) + const auto indexes = comicsModel->supportedIndexes(selectionProvider(), ComicAction::DeleteMetadata); + if (indexes.isEmpty()) return; - for (auto &comic : comics) - comic.info.deleteMetadata(); - - DBHelper::updateComicsInfo(comics, foldersModel->getDatabase()); - comicsModel->reload(); + comicsModel->deleteMetadata(indexes); } void ComicManagementCoordinator::deleteSelectedComics() { - const auto comicIds = selectedComicIds(); - if (comicIds.isEmpty()) + // a smart list has no entries, so its comics are deleted from the disk like the comics of a folder + const auto listIndex = currentListProvider(); + const auto listType = listIndex.data(ReadingListModel::TypeListsRole).toInt(); + // a folder of lists shows no comics + if (listIndex.isValid() && (listType == ReadingListModel::ReadingListFolder || listType == ReadingListModel::SmartListFolder)) return; - + const bool fromList = listIndex.isValid() && listType != ReadingListModel::SmartList; const auto source = currentSource(); - const auto listIndex = currentListProvider(); - if (listIndex.isValid()) { - deleteComicsFromList(comicIds, - source, - listIndex.data(ReadingListModel::TypeListsRole).toInt(), - listIndex.data(ReadingListModel::IDRole).toULongLong()); - } else { - deleteComicsFromDisk(comicIds, source); + if (fromList) { + const auto rowIds = selectedRowIds(ComicAction::RemoveFromList); + if (!rowIds.isEmpty()) + deleteComicsFromList(rowIds, + source, + listIndex.data(ReadingListModel::TypeListsRole).toInt(), + listIndex.data(ReadingListModel::IDRole).toULongLong()); + return; } + + const auto fileRowIds = selectedRowIds(ComicAction::ChangeFile); + const auto missingRowIds = selectedRowIds(ComicAction::RemoveFromFolder); + if (!fileRowIds.isEmpty() || !missingRowIds.isEmpty()) + deleteComicsFromDisk(fileRowIds, missingRowIds, source); } void ComicManagementCoordinator::saveSelectedCoversTo() { - const auto comicIds = selectedComicIds(); - if (comicIds.isEmpty()) + const auto rowIds = selectedRowIds(ComicAction::SaveCover); + if (rowIds.isEmpty()) return; const auto source = currentSource(); @@ -362,10 +468,15 @@ void ComicManagementCoordinator::saveSelectedCoversTo() if (destinationFolder.isEmpty()) return; - const auto indexList = indexesForComicIds(comicIds, source); + const auto indexList = indexesForRows(rowIds, source); for (const auto &comic : indexList) { QString origin = comic.data(ComicModel::CoverPathRole).toString().remove("file:///").remove("file:"); - const auto destination = QDir(destinationFolder).filePath(comic.data(ComicModel::FileNameRole).toString() + ".jpg"); + auto name = comic.data(ComicModel::FileNameRole).toString(); + if (name.isEmpty()) + name = comic.data(ComicModel::LastKnownNameRole).toString(); + if (name.isEmpty()) + name = comic.data(ComicModel::ReadableTitle).toString(); + const auto destination = QDir(destinationFolder).filePath(name + ".jpg"); QLOG_DEBUG() << "From : " << origin; QLOG_DEBUG() << "To : " << destination; @@ -374,13 +485,13 @@ void ComicManagementCoordinator::saveSelectedCoversTo() } } -void ComicManagementCoordinator::setCustomCover(qulonglong comicId, const QString &imagePath) +void ComicManagementCoordinator::setCustomCover(const ComicRowId &row, const QString &imagePath) { if (!comicOpeningAllowedProvider()) return; - const auto index = comicsModel->getIndexFromId(comicId); - if (!index.isValid()) + const auto index = comicsModel->indexOf(row); + if (!comicsModel->supports(index, ComicAction::SetCustomCover)) return; const QImage cover(imagePath); @@ -391,32 +502,229 @@ void ComicManagementCoordinator::setCustomCover(qulonglong comicId, const QStrin return; } - auto comic = comicsModel->getComic(index); - const auto coverPath = YACReader::LibraryPaths::coverPath(libraryPathProvider(), comic.info.hash); - if (!YACReader::saveCover(coverPath, cover)) { + const auto libraryPath = libraryPathProvider(); + auto record = comicsModel->record(index); + if (const auto *placeholder = recordPlaceholder(record)) { + QHash> changedCovers; + if (changePlaceholderCover(*placeholder, cover, changedCovers)) + comicsModel->notifyPlaceholderCoverChange(changedCovers); + else + QMessageBox::warning(window, + QCoreApplication::translate("LibraryWindow", "Error saving cover"), + QCoreApplication::translate("LibraryWindow", "There was an error saving the cover image.")); + return; + } + auto &info = *recordInfo(record); + info.coverPage = QVariant(); + info.originalCoverSize = QStringLiteral("%1x%2").arg(cover.width()).arg(cover.height()); + info.coverSizeRatio = static_cast(cover.width()) / cover.height(); + info.lastTimeCoverSet = QDateTime::currentSecsSinceEpoch(); + info.usesExternalCover = true; + + // the previous cover stays when any step fails + StagedCover staged(YACReader::LibraryPaths::coverPath(libraryPath, info.hash)); + bool saved = staged.stage(cover); + if (saved) { + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(YACReader::LibraryPaths::libraryDataPath(libraryPath)); + db.transaction(); + saved = DBHelper::loadRecord(record, db) && DBHelper::updateComicInfoCover(info, db) && staged.install() && db.commit(); + if (saved) { + staged.finish(); + } else { + db.rollback(); + staged.restore(); + } + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + } + + if (!saved) { QMessageBox::warning(window, QCoreApplication::translate("LibraryWindow", "Error saving cover"), QCoreApplication::translate("LibraryWindow", "There was an error saving the cover image.")); return; } - comic.info.coverPage = QVariant(); - comic.info.originalCoverSize = QStringLiteral("%1x%2").arg(cover.width()).arg(cover.height()); - comic.info.coverSizeRatio = static_cast(cover.width()) / cover.height(); - comic.info.lastTimeCoverSet = QDateTime::currentSecsSinceEpoch(); - comic.info.usesExternalCover = true; - DBHelper::update(libraryIdProvider(), comic.info); - comicsModel->notifyCoverChange(comic); + comicsModel->notifyCoverChange({ info.id }); } +// a placeholder's cover is always its own image bool ComicManagementCoordinator::hasCustomCoverInSelection() const { - const auto comics = comicsModel->getComics(selectionProvider()); + const auto indexes = comicsModel->supportedIndexes(selectionProvider(), ComicAction::SetCustomCover); + const auto hasPlaceholderCover = std::any_of(indexes.cbegin(), indexes.cend(), [this](const QModelIndex &index) { + const auto record = comicsModel->record(index); + const auto *placeholder = recordPlaceholder(record); + return placeholder != nullptr && placeholder->coverKey.has_value(); + }); + if (hasPlaceholderCover) + return true; + + const auto comics = comicsModel->getComics(indexes); return std::any_of(comics.cbegin(), comics.cend(), [](const ComicDB &comic) { return comic.info.usesExternalCover.toBool(); }); } +bool ComicManagementCoordinator::canMatchSelectionToLibraryComic() const +{ + const auto selection = selectionProvider(); + if (selection.size() == 1) + return comicsModel->supports(selection.constFirst(), ComicAction::MatchToLibraryComic); + return selection.size() > 1 && std::all_of(selection.cbegin(), selection.cend(), [this](const QModelIndex &index) { return comicsModel->listEntry(index).has_value(); }); +} + +// the comic is given to the entries of the missing comic in every list +void ComicManagementCoordinator::matchSelectedToLibraryComic() +{ + if (!comicOpeningAllowedProvider() || !canMatchSelectionToLibraryComic()) + return; + + const auto selection = selectionProvider(); + if (selection.size() > 1) { + matchSelectedEntriesToLibraryComics(selection); + return; + } + + const auto index = selection.constFirst(); + const auto record = comicsModel->record(index); + const auto entry = comicsModel->listEntry(index); + // a missing comic of a folder is matched to a comic of that folder + const auto rowId = comicsModel->rowId(index); + const auto folderId = std::holds_alternative(rowId) ? std::optional(comicsModel->getSourceId()) : std::nullopt; + if (!entry && !folderId) + return; + MissingComic missing; + if (const auto *placeholder = recordPlaceholder(record)) + missing = placeholder->id; + else if (const auto *info = recordInfo(record)) + missing = info->id; + const auto source = currentSource(); + const auto dataPath = YACReader::LibraryPaths::libraryDataPath(source.libraryPath); + + emit currentSourceRefreshStarted(); + CblMatchComicDialog dialog(recordMetadata(record), source.libraryPath, folderId, window); + if (dialog.exec() != QDialog::Accepted || !dialog.chosenComic() || !isCurrentSource(source)) { + emit currentSourceRefreshCancelled(); + return; + } + const auto comicInfoId = dialog.chosenComic()->id; + + std::optional otherLists; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(dataPath); + otherLists = entry ? otherListsWithEntriesOf({ missing }, entry->id.listKind, { entry->listId }, db) + : otherListsWithEntriesOf({ missing }, ListKind::ReadingList, { }, db); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + if (otherLists && !otherLists->isEmpty()) { + const auto answer = QMessageBox::question(window, + QCoreApplication::translate("LibraryWindow", "Match to library comic"), + QCoreApplication::translate("LibraryWindow", "This also gives the comic to entries in %n other list(s): %1", "", otherLists->size()).arg(otherLists->join(QStringLiteral(", "))), + QMessageBox::Apply | QMessageBox::Cancel, + QMessageBox::Apply); + if (answer != QMessageBox::Apply) { + emit currentSourceRefreshCancelled(); + return; + } + } + + bool matched = false; + ComicMatchFiles files; + if (otherLists) { + { + QSqlDatabase db = DataBaseManagement::loadDatabase(dataPath); + db.transaction(); + matched = std::visit(overloaded { + [&](PlaceholderId id) { return matchPlaceholderToComic(id, comicInfoId, dataPath, db, files); }, + [&](ComicInfoId id) { return matchDeletedComicToComic(id, comicInfoId, db, files); }, + }, + missing) && + db.commit(); + if (!matched) + db.rollback(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + finishComicMatchFiles(dataPath, files, matched); + } + + if (!matched) { + emit currentSourceRefreshCancelled(); + QMessageBox::warning(window, + QCoreApplication::translate("LibraryWindow", "Match to library comic"), + QCoreApplication::translate("LibraryWindow", "The comic could not be matched. Nothing was changed.")); + return; + } + emit currentSourceRefreshAccepted(); +} + +void ComicManagementCoordinator::matchSelectedEntriesToLibraryComics(const QModelIndexList &selection) +{ + QList entries; + for (const auto &index : selection) { + if (const auto entry = comicsModel->listEntry(index)) + entries.append(cblListEntry(comicsModel->record(index), *entry)); + } + const auto source = currentSource(); + + emit currentSourceRefreshStarted(); + CblImportDialog dialog(currentListProvider().data().toString(), entries, source.libraryPath, window); + bool changed = false; + connect(&dialog, &CblImportDialog::listEntriesChanged, this, [&changed] { changed = true; }); + dialog.exec(); + if (changed) + emit currentSourceRefreshAccepted(); + else + emit currentSourceRefreshCancelled(); +} + +// a new image gets a new file; the old file is removed after the commit, the new one on a failure. +// A null `cover` clears the cover of the placeholder. +bool ComicManagementCoordinator::changePlaceholderCover(const Placeholder &placeholder, const QImage &cover, QHash> &changedCovers) +{ + const auto dataPath = YACReader::LibraryPaths::libraryDataPath(libraryPathProvider()); + std::optional newKey; + if (!cover.isNull()) { + newKey = savePlaceholderCover(dataPath, cover); + if (!newKey) + return false; + } + + std::optional oldKey; + bool saved = false; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(dataPath); + db.transaction(); + const auto stored = DBHelper::loadRecord(ComicRecordCase::Placeholder { placeholder }, db); + if (stored) + oldKey = recordPlaceholder(*stored)->coverKey; + saved = stored && DBHelper::updatePlaceholderCover(placeholder.id, newKey, db) && db.commit(); + if (!saved) + db.rollback(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + if (!saved) { + if (newKey) + DBHelper::removePlaceholderCovers(dataPath, { *newKey }); + return false; + } + if (oldKey) + DBHelper::removePlaceholderCovers(dataPath, { *oldKey }); + changedCovers.insert(placeholder.id, newKey); + return true; +} + +// a deleted comic keeps its custom cover: the original cannot be extracted without the file void ComicManagementCoordinator::resetSelectedCustomCovers() { if (!comicOpeningAllowedProvider()) @@ -424,13 +732,27 @@ void ComicManagementCoordinator::resetSelectedCustomCovers() const auto libraryPath = libraryPathProvider(); const auto libraryId = libraryIdProvider(); - const auto indexes = selectionProvider(); + const auto indexes = comicsModel->supportedIndexes(selectionProvider(), ComicAction::SetCustomCover); + QSet clearedPlaceholders; + QHash> changedPlaceholderCovers; + QList changedCovers; for (const auto &index : indexes) { - auto comic = comicsModel->getComic(index); - if (!comic.info.usesExternalCover.toBool()) + // a placeholder goes back to no cover, which shows the default image + const auto record = comicsModel->record(index); + if (const auto *placeholder = recordPlaceholder(record)) { + if (placeholder->coverKey && !clearedPlaceholders.contains(placeholder->id)) { + clearedPlaceholders.insert(placeholder->id); + changePlaceholderCover(*placeholder, QImage(), changedPlaceholderCovers); + } continue; + } - YACReader::InitialComicInfoExtractor extractor(QDir::cleanPath(libraryPath + comic.path), QString(), 1); + auto loaded = comicsModel->getComic(index); + if (!loaded || !loaded->info.usesExternalCover.toBool()) + continue; + auto &comic = *loaded; + + YACReader::InitialComicInfoExtractor extractor(QDir::cleanPath(libraryPath + comic.file.path), QString(), 1); extractor.extract(); if (!extractor.hasValidCover()) continue; @@ -447,8 +769,10 @@ void ComicManagementCoordinator::resetSelectedCustomCovers() comic.info.lastTimeCoverSet = QDateTime::currentSecsSinceEpoch(); comic.info.usesExternalCover = false; DBHelper::update(libraryId, comic.info); - comicsModel->notifyCoverChange(comic); + changedCovers.append(comic.info.id); } + comicsModel->notifyCoverChange(changedCovers); + comicsModel->notifyPlaceholderCoverChange(changedPlaceholderCovers); } QProgressDialog *ComicManagementCoordinator::newProgressDialog(const QString &label, int maximum) @@ -479,14 +803,14 @@ void ComicManagementCoordinator::processComicFiles(ComicFilesManager *comicFiles thread->start(); } -QList ComicManagementCoordinator::selectedComicIds() const +QList ComicManagementCoordinator::selectedRowIds(ComicAction action) const { - QList comicIds; - const auto selection = selectionProvider(); - comicIds.reserve(selection.size()); + QList rowIds; + const auto selection = comicsModel->supportedIndexes(selectionProvider(), action); + rowIds.reserve(selection.size()); for (const auto &index : selection) - comicIds.append(index.data(ComicModel::IdRole).toULongLong()); - return comicIds; + rowIds.append(comicsModel->rowId(index)); + return rowIds; } ComicManagementCoordinator::SourceContext ComicManagementCoordinator::currentSource() const @@ -494,12 +818,12 @@ ComicManagementCoordinator::SourceContext ComicManagementCoordinator::currentSou return { libraryPathProvider(), static_cast(comicsModel->getMode()), comicsModel->getSourceId() }; } -QModelIndexList ComicManagementCoordinator::indexesForComicIds(const QList &comicIds, const SourceContext &source) const +QModelIndexList ComicManagementCoordinator::indexesForRows(const QList &rowIds, const SourceContext &source) const { if (!isCurrentSource(source)) return { }; - auto indexes = comicsModel->getIndexesFromIds(comicIds); + auto indexes = comicsModel->indexesOf(rowIds); if (std::any_of(indexes.cbegin(), indexes.cend(), [](const QModelIndex &index) { return !index.isValid(); })) return { }; @@ -514,17 +838,32 @@ bool ComicManagementCoordinator::isCurrentSource(const SourceContext &source) co return QDir::cleanPath(foldersModel->getDatabase()) == QDir::cleanPath(YACReader::LibraryPaths::libraryDataPath(source.libraryPath)) && static_cast(comicsModel->getMode()) == source.mode && comicsModel->getSourceId() == source.sourceId; } -void ComicManagementCoordinator::deleteComicsFromDisk(const QList &comicIds, const SourceContext &source) +void ComicManagementCoordinator::deleteComicsFromDisk(const QList &rowIds, const QList &missingRowIds, const SourceContext &source) { + QString question; + if (missingRowIds.isEmpty()) + question = QCoreApplication::translate("LibraryWindow", "All the selected comics will be deleted from your disk. Are you sure?"); + else if (rowIds.isEmpty()) + question = QCoreApplication::translate("LibraryWindow", "The selected missing comics will be removed from this folder. Are you sure?"); + else + question = QCoreApplication::translate("LibraryWindow", "The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure?"); const auto answer = QMessageBox::question(window, QCoreApplication::translate("LibraryWindow", "Delete comics"), - QCoreApplication::translate("LibraryWindow", "All the selected comics will be deleted from your disk. Are you sure?"), + question, QMessageBox::Yes, QMessageBox::No); if (answer != QMessageBox::Yes) return; - auto indexList = indexesForComicIds(comicIds, source); + if (!missingRowIds.isEmpty()) { + const auto missingIndexes = indexesForRows(missingRowIds, source); + if (!missingIndexes.isEmpty()) + comicsModel->removeMissingComicsFromFolder(missingIndexes); + } + if (rowIds.isEmpty()) + return; + + auto indexList = indexesForRows(rowIds, source); auto comics = comicsModel->getComics(indexList); if (comics.isEmpty()) return; @@ -532,13 +871,13 @@ void ComicManagementCoordinator::deleteComicsFromDisk(const QList &c QList paths; paths.reserve(comics.size()); for (const auto &comic : std::as_const(comics)) { - paths.append(source.libraryPath + comic.path); - QLOG_TRACE() << comic.path; - QLOG_TRACE() << comic.id; - QLOG_TRACE() << comic.parentId; + paths.append(source.libraryPath + comic.file.path); + QLOG_TRACE() << comic.file.path; + QLOG_TRACE() << comic.file.id.value(); + QLOG_TRACE() << comic.file.folderId.value(); } - auto remover = new ComicsRemover(indexList, paths, comics.constFirst().parentId); + auto remover = new ComicsRemover(indexList, paths, comics.constFirst().file.folderId.value()); auto thread = new QThread(this); moveAndConnectRemoverToThread(remover, thread); @@ -554,11 +893,24 @@ void ComicManagementCoordinator::deleteComicsFromDisk(const QList &c thread->start(); } -void ComicManagementCoordinator::deleteComicsFromList(const QList &comicIds, const SourceContext &source, int listType, qulonglong listId) +void ComicManagementCoordinator::deleteComicsFromList(const QList &rowIds, const SourceContext &source, int listType, qulonglong listId) { + auto question = QCoreApplication::translate("LibraryWindow", "Comics will only be deleted from the current label/list. Are you sure?"); + if (listType == ReadingListModel::SpecialList) { + switch (static_cast(listId)) { + case ReadingListModel::TypeSpecialList::Reading: + question = QCoreApplication::translate("LibraryWindow", "Remove the comics from Reading? Their reading progress will be reset."); + break; + case ReadingListModel::TypeSpecialList::Favorites: + question = QCoreApplication::translate("LibraryWindow", "The comics will be removed from Favorites. Are you sure?"); + break; + case ReadingListModel::TypeSpecialList::Recent: + break; + } + } const auto answer = QMessageBox::question(window, QCoreApplication::translate("LibraryWindow", "Remove comics"), - QCoreApplication::translate("LibraryWindow", "Comics will only be deleted from the current label/list. Are you sure?"), + question, QMessageBox::Yes, QMessageBox::No); if (answer != QMessageBox::Yes) @@ -568,7 +920,7 @@ void ComicManagementCoordinator::deleteComicsFromList(const QList &c if (!currentList.isValid() || currentList.data(ReadingListModel::TypeListsRole).toInt() != listType || currentList.data(ReadingListModel::IDRole).toULongLong() != listId) return; - const auto indexList = indexesForComicIds(comicIds, source); + const auto indexList = indexesForRows(rowIds, source); if (indexList.isEmpty()) return; @@ -577,12 +929,13 @@ void ComicManagementCoordinator::deleteComicsFromList(const QList &c comicsModel->deleteComicsFromSpecialList(indexList, listId); break; case ReadingListModel::Label: - comicsModel->deleteComicsFromLabel(indexList, listId); - break; case ReadingListModel::ReadingList: - comicsModel->deleteComicsFromReadingList(indexList, listId); + comicsModel->deleteComicsFromLists(indexList); break; case ReadingListModel::Separator: + case ReadingListModel::SmartList: + case ReadingListModel::ReadingListFolder: + case ReadingListModel::SmartListFolder: break; } } @@ -593,7 +946,7 @@ void ComicManagementCoordinator::finishComicDeletion() if (comicDeletionFailed) { QMessageBox::critical(window, QCoreApplication::translate("LibraryWindow", "Unable to delete"), - QCoreApplication::translate("LibraryWindow", "There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder.")); + QCoreApplication::translate("LibraryWindow", "There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder.")); } comicDeletionFailed = false; } diff --git a/YACReaderLibrary/comic_management_coordinator.h b/YACReaderLibrary/comic_management_coordinator.h index ebaf0be66..0a61271a0 100644 --- a/YACReaderLibrary/comic_management_coordinator.h +++ b/YACReaderLibrary/comic_management_coordinator.h @@ -18,6 +18,7 @@ class ComicVineDialog; class FolderModel; class FolderModelProxy; class PropertiesDialog; +class QImage; class QProgressDialog; class QSettings; class QWidget; @@ -51,6 +52,11 @@ class ComicManagementCoordinator : public QObject LibraryPathProvider libraryPathProvider); bool hasCustomCoverInSelection() const; + // one missing entry or deleted comic, or several entries of a list + bool canMatchSelectionToLibraryComic() const; + // the folder the comics view shows, if it shows one + std::optional shownFolderId() const; + std::optional shownReadingListId() const; public slots: void copyAndImportComicsToCurrentFolder(const QList> &comics); @@ -63,6 +69,8 @@ public slots: void openComic(const ComicDB &comic, ComicModel::Mode mode); void openContainingFolderOfCurrentComic(); void showComicVineScraper(); + void trackMissingIssues(qulonglong folderId); + void trackMissingIssuesInReadingList(qulonglong readingListId); void showProperties(); void setSelectedComicsRead(); void setSelectedComicsUnread(); @@ -72,8 +80,9 @@ public slots: void deleteMetadataFromSelectedComics(); void deleteSelectedComics(); void saveSelectedCoversTo(); - void setCustomCover(qulonglong comicId, const QString &imagePath); + void setCustomCover(const ComicRowId &row, const QString &imagePath); void resetSelectedCustomCovers(); + void matchSelectedToLibraryComic(); void setComicUnread(qulonglong libraryId, const ComicDB &comic); @@ -83,7 +92,7 @@ public slots: void currentSourceRefreshStarted(); void currentSourceRefreshAccepted(); void currentSourceRefreshCancelled(); - void comicNumbersAssigned(qint64 editedComicId); + void comicNumbersAssigned(const ComicRowId &firstRow); void comicDeletionFinished(); void rootContinueReadingReloadRequested(); @@ -94,6 +103,11 @@ public slots: qulonglong sourceId; }; + // entryId: the reading list entry of the opened row, 0 if none + void startReader(const ComicDB &comic, ComicModel::Mode mode, qulonglong entryId); + // on success, adds the new cover key of the placeholder to `changedCovers` + bool changePlaceholderCover(const Placeholder &placeholder, const QImage &cover, QHash> &changedCovers); + void matchSelectedEntriesToLibraryComics(const QModelIndexList &selection); QProgressDialog *newProgressDialog(const QString &label, int maximum); void copyAndImportComics(const QList> &comics, const QModelIndex &destinationFolder, @@ -102,12 +116,15 @@ public slots: const QModelIndex &destinationFolder, const QString &libraryPath); void processComicFiles(ComicFilesManager *comicFilesManager, QProgressDialog *progressDialog); - QList selectedComicIds() const; + // the selected rows that support the action + QList selectedRowIds(ComicAction action) const; SourceContext currentSource() const; - QModelIndexList indexesForComicIds(const QList &comicIds, const SourceContext &source) const; + // empty when the source changed or a row is no longer in the model + QModelIndexList indexesForRows(const QList &rowIds, const SourceContext &source) const; bool isCurrentSource(const SourceContext &source) const; - void deleteComicsFromDisk(const QList &comicIds, const SourceContext &source); - void deleteComicsFromList(const QList &comicIds, const SourceContext &source, int listType, qulonglong listId); + // the files of `rowIds` are deleted from the disk; the missing comics of `missingRowIds` are removed from the folder + void deleteComicsFromDisk(const QList &rowIds, const QList &missingRowIds, const SourceContext &source); + void deleteComicsFromList(const QList &rowIds, const SourceContext &source, int listType, qulonglong listId); void finishComicDeletion(); QWidget *window; diff --git a/YACReaderLibrary/comic_vine/CMakeLists.txt b/YACReaderLibrary/comic_vine/CMakeLists.txt index 382e3f0f9..b68bf5dee 100644 --- a/YACReaderLibrary/comic_vine/CMakeLists.txt +++ b/YACReaderLibrary/comic_vine/CMakeLists.txt @@ -68,5 +68,6 @@ target_link_libraries(comic_vine PUBLIC common_gui custom_widgets_library db_helper + library_common QsLog ) diff --git a/YACReaderLibrary/comic_vine/api_key_dialog.cpp b/YACReaderLibrary/comic_vine/api_key_dialog.cpp index 9aa581e7a..0de61b82a 100644 --- a/YACReaderLibrary/comic_vine/api_key_dialog.cpp +++ b/YACReaderLibrary/comic_vine/api_key_dialog.cpp @@ -18,7 +18,7 @@ ApiKeyDialog::ApiKeyDialog(QWidget *parent) settings = new QSettings(YACReader::getSettingsPath() + "/YACReaderLibrary.ini", QSettings::IniFormat); // TODO unificar la creación del fichero de config con el servidor settings->beginGroup("ComicVine"); - QLabel *info = new QLabel(tr("Before you can connect to Comic Vine, you need your own API key. Please, get one free here")); + QLabel *info = new QLabel(tr("Before you can connect to Comic Vine, you need your own API key. Please get one free here")); info->setWordWrap(true); info->setOpenExternalLinks(true); edit = new QLineEdit(); diff --git a/YACReaderLibrary/comic_vine/comic_vine_client.cpp b/YACReaderLibrary/comic_vine/comic_vine_client.cpp index 888bfa487..dacc7561f 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_client.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_client.cpp @@ -31,7 +31,7 @@ static const QString CV_SERIES_DETAIL = CV_WEB_ADDRESS + "/volume/4050-%1/?api_k // gets info for comics in a volume id %1 static const QString CV_COMICS_INFO = CV_WEB_ADDRESS + "/issues/?api_key=" + CV_API_KEY + - "&limit=1000&format=json&field_list=name,issue_number,id,image&filter=volume:%1" + "&limit=1000&format=json&field_list=name,issue_number,id,image,cover_date,volume&filter=volume:%1" "&sort=cover_date:asc" // sorting by cover_date, because comic vine doesn't use natural sorting (issue_number -> 1 10 11 ... 100 2 20 21....) "&offset=%2"; @@ -227,6 +227,19 @@ QByteArray ComicVineClient::getStoryArcDetail(const QString &id, bool &outError, return result; } +QByteArray ComicVineClient::getImage(const QString &url, bool &outError, bool &outTimeout) +{ + HttpWorker *search = new HttpWorker(url, userAgent); + search->get(); + search->wait(); + outError = !(search->wasValid()); + outTimeout = search->wasTimeout(); + QByteArray result = search->getResult(); + delete search; + + return result; +} + void ComicVineClient::getComicCover(const QString &url) { auto search = new HttpWorker(url, userAgent); diff --git a/YACReaderLibrary/comic_vine/comic_vine_client.h b/YACReaderLibrary/comic_vine/comic_vine_client.h index b47955d8a..4171640fa 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_client.h +++ b/YACReaderLibrary/comic_vine/comic_vine_client.h @@ -31,6 +31,7 @@ public slots: void getAllVolumeComicsInfo(const QString &idVolume); QByteArray getComicDetail(const QString &id, bool &outError, bool &outTimeout); QByteArray getStoryArcDetail(const QString &id, bool &outError, bool &outTimeout); + QByteArray getImage(const QString &url, bool &outError, bool &outTimeout); void getComicCover(const QString &url); void getComicId(const QString &id, int comicNumber); diff --git a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp index 2e68974ec..3cba88680 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp @@ -5,6 +5,7 @@ #include "comic_vine_json_parser.h" #include "data_base_management.h" #include "db_helper.h" +#include "placeholder_cover.h" #include "response_parser.h" #include "scraper_lineedit.h" #include "search_single_comic.h" @@ -19,6 +20,9 @@ #include #include +#include +#include +#include #include #include #include @@ -50,6 +54,13 @@ void ComicVineDialog::closeEvent(QCloseEvent *event) clearState(); } +void ComicVineDialog::done(int result) +{ + if (result == QDialog::Rejected) + run++; + QDialog::done(result); +} + void ComicVineDialog::doLayout() { skipButton = new QPushButton(tr("skip")); @@ -119,15 +130,15 @@ void ComicVineDialog::goNext() // if (content->currentWidget() == seriesQuestionWidget) { if (seriesQuestionWidget->getYes()) { - QString volumeSearchString = comics[0].getParentFolderName(); + QString volumeSearchString = parentFolderName(recordPath(comics[0])); mode = ScraperMode::Volume; showSearchVolume(volumeSearchString); } else { status = ScraperStatus::AutoSearching; mode = ScraperMode::SingleComicInList; - ComicDB comic = comics[currentIndex]; - QString title = comic.getTitleOrFileName(); + const auto &comic = comics[currentIndex]; + QString title = titleOrFileName(recordMetadata(comic), recordFileName(comic)); titleHeader->setSubTitle(tr("comic %1 of %2 - %3").arg(currentIndex + 1).arg(comics.length()).arg(title)); showLoading(tr("Looking for volume...")); @@ -141,11 +152,12 @@ void ComicVineDialog::goNext() } else if (content->currentWidget() == sortVolumeComicsWidget) { showLoading(); - // ComicDB-ComicVineID - QList> matchingInfo = sortVolumeComicsWidget->getMatchingInfo(); + // comic - Comic Vine id + QList> matchingInfo = sortVolumeComicsWidget->getMatchingInfo(); + const auto missingIssues = folderId && sortVolumeComicsWidget->addsMissingIssues() ? sortVolumeComicsWidget->getMissingIssues() : QStringList(); auto volumeInfo = selectVolumeWidget->getSelectedVolumeInfo(); - QtConcurrent::run(&ComicVineDialog::getComicsInfo, this, matchingInfo, volumeInfo); + QtConcurrent::run(&ComicVineDialog::getComicsInfo, this, matchingInfo, missingIssues, volumeInfo, run.load()); } else if (content->currentWidget() == selectComicWidget) { showLoading(); @@ -194,7 +206,7 @@ void ComicVineDialog::goBack() } } -void ComicVineDialog::setComics(const QList &comics) +void ComicVineDialog::setComics(const QList &comics) { this->comics = comics; } @@ -226,6 +238,11 @@ QSize ComicVineDialog::minimumSizeHint() const void ComicVineDialog::show() { + run++; + failedComics = 0; + basicMissingIssues = 0; + missingIssuesWithoutCover = 0; + limitReached = false; QDialog::show(); currentIndex = 0; @@ -238,9 +255,9 @@ void ComicVineDialog::show() status = ScraperStatus::AskingForInfo; mode = ScraperMode::SingleComic; - ComicDB singleComic = comics[0]; + const auto &singleComic = comics[0]; - QString title = singleComic.getTitleOrFileName(); + QString title = titleOrFileName(recordMetadata(singleComic), recordFileName(singleComic)); titleHeader->setSubTitle(title); showSearchSingleComic(volumeSearchStringFromComic(singleComic)); @@ -276,7 +293,7 @@ void ComicVineDialog::processClientResults(const QString &string) p.loadJSONResponse(string); // QMessageBox::information(0,"Result", QString("Number of results : %1").arg(p.getNumResults())); if (p.responseError()) { - QMessageBox::critical(0, tr("Error connecting to ComicVine"), p.errorDescription()); + QMessageBox::critical(0, tr("Error connecting to Comic Vine"), p.errorDescription()); goBack(); } else { @@ -394,6 +411,7 @@ void ComicVineDialog::showSortVolumeComics(const QString &json) content->setCurrentWidget(sortVolumeComicsWidget); sortVolumeComicsWidget->setData(comics, json, currentVolumeId); + sortVolumeComicsWidget->setMissingIssuesOption(folderId.has_value()); backButton->setVisible(true); nextButton->setVisible(true); @@ -438,40 +456,139 @@ void ComicVineDialog::queryTimeOut() } } -void ComicVineDialog::getComicsInfo(QList> matchingInfo, const SelectedVolumeInfo &volumeInfo) +void ComicVineDialog::getComicsInfo(QList> matchingInfo, const QStringList &missingIssues, const SelectedVolumeInfo &volumeInfo, int runId) { - QList comics; + QList> patches; const auto &matches = matchingInfo; for (auto match : matches) { + if (run != runId) + return; auto comicVineClient = new ComicVineClient; // connect(comicVineClient,SIGNAL(searchResult(QString)),this,SLOT(debugClientResults(QString))); // connect(comicVineClient,SIGNAL(timeOut()),this,SLOT(queryTimeOut())); // connect(comicVineClient,SIGNAL(finished()),comicVineClient,SLOT(deleteLater())); + if (limitReached) { + failedComics++; + continue; + } bool error; bool timeout; - QByteArray result = comicVineClient->getComicDetail(match.second, error, timeout); // TODO check timeOut or Connection error - if (error || timeout) - continue; // TODO - ComicDB comic = YACReader::parseCVJSONComicInfo(match.first, result, volumeInfo); // TODO check result error - comic.info.comicVineID = match.second; - comics.push_back(comic); - - setLoadingMessage(tr("Retrieving tags for : %1").arg(match.first.getFileName())); + QByteArray result = comicVineClient->getComicDetail(match.second, error, timeout); + if (!detailAnswered(error, timeout, result)) { + failedComics++; + continue; + } + auto patch = YACReader::parseCVJSONComicInfo(result, volumeInfo); + patch.set(&DescriptiveComicMetadata::comicVineID, match.second); + patches.append({ match.first, patch }); + + setLoadingMessage(tr("Retrieving tags for: %1").arg(recordFileName(match.first))); } - DBHelper::updateComicsInfo(comics, databasePath); + QList missing; + for (int i = 0; i < missingIssues.size(); i++) { + if (run != runId) { + removeCovers(missing); + return; + } + setLoadingMessage(tr("Creating missing comic %1 of %2").arg(i + 1).arg(missingIssues.size())); + missing.append(missingIssue(missingIssues.at(i), volumeInfo)); + } + if (run != runId) { + removeCovers(missing); + return; + } + + saveMetadata(patches, missing); finishSuccessfully(); } +// the full tags of the issue, or the values of the volume's list when they cannot be read; and its cover +MissingIssue ComicVineDialog::missingIssue(const QString &issueJson, const SelectedVolumeInfo &volumeInfo) +{ + const auto issue = QJsonDocument::fromJson(issueJson.toUtf8()).object(); + MissingIssue result; + result.comicVineId = QString::number(issue.value("id").toInteger()); + + ComicVineClient comicVineClient; + bool error; + bool timeout; + auto image = issue.value("image").toObject(); + if (!limitReached) { + const auto detail = comicVineClient.getComicDetail(result.comicVineId, error, timeout); + if (detailAnswered(error, timeout, detail)) { + result.patch = YACReader::parseCVJSONComicInfo(detail, volumeInfo); + const auto detailImage = QJsonDocument::fromJson(detail).object().value("results").toObject().value("image").toObject(); + if (!detailImage.isEmpty()) + image = detailImage; + } + } + if (result.patch.isEmpty()) { + basicMissingIssues++; + const QJsonObject list { { "number_of_total_results", 1 }, { "results", issue } }; + result.patch = YACReader::parseCVJSONComicInfo(QString::fromUtf8(QJsonDocument(list).toJson()), volumeInfo); + } + result.patch.set(&DescriptiveComicMetadata::comicVineID, result.comicVineId); + + auto coverUrl = image.value("super_url").toString(); + if (coverUrl.isEmpty()) + coverUrl = image.value("medium_url").toString(); + if (!coverUrl.isEmpty()) { + const auto cover = comicVineClient.getImage(coverUrl, error, timeout); + if (!error && !timeout) + result.coverKey = savePlaceholderCover(databasePath, QImage::fromData(cover)); + if (!result.coverKey) + missingIssuesWithoutCover++; + } + return result; +} + +bool ComicVineDialog::detailAnswered(bool error, bool timeout, const QByteArray &detail) +{ + const auto answer = YACReader::comicVineAnswer(error, timeout, detail); + if (answer == YACReader::ComicVineAnswer::LimitReached) + limitReached = true; + return answer == YACReader::ComicVineAnswer::Answered; +} + +// only when something failed +void ComicVineDialog::showFailures() +{ + QStringList lines; + if (failedComics > 0) + lines.append(tr("Comic Vine did not answer for %n comic(s).", "", failedComics)); + if (basicMissingIssues > 0) + lines.append(tr("%n missing comic(s) got only the basic data of the volume.", "", basicMissingIssues)); + if (missingIssuesWithoutCover > 0) + lines.append(tr("%n missing comic(s) have no cover.", "", missingIssuesWithoutCover)); + if (lines.isEmpty()) + return; + + lines.append(limitReached ? tr("Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated.") + : tr("Try again later for the comics that were not updated.")); + QMessageBox::warning(parentWidget(), tr("Comic Vine"), lines.join(QLatin1Char('\n'))); +} + +void ComicVineDialog::removeCovers(const QList &missingIssues) +{ + QStringList coverKeys; + for (const auto &issue : missingIssues) { + if (issue.coverKey) + coverKeys.append(*issue.coverKey); + } + DBHelper::removePlaceholderCovers(databasePath, coverKeys); +} + void ComicVineDialog::getComicInfo(const QString &comicId, const SelectedVolumeInfo &volumeInfo) { auto comicVineClient = new ComicVineClient; bool error; bool timeout; - QByteArray result = comicVineClient->getComicDetail(comicId, error, timeout); // TODO check timeOut or Connection error - if (error || timeout) { + QByteArray result = comicVineClient->getComicDetail(comicId, error, timeout); + if (!detailAnswered(error, timeout, result)) { + failedComics++; if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) finishSuccessfully(); else @@ -479,21 +596,10 @@ void ComicVineDialog::getComicInfo(const QString &comicId, const SelectedVolumeI return; } - ComicDB comic = YACReader::parseCVJSONComicInfo(comics[currentIndex], result, volumeInfo); // TODO check result error - comic.info.comicVineID = comicId; - setLoadingMessage(tr("Retrieving tags for : %1").arg(comics[currentIndex].getFileName())); - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - db.open(); - db.transaction(); - - DBHelper::update(&(comic.info), db); - - db.commit(); - connectionName = db.connectionName(); - } - QSqlDatabase::removeDatabase(connectionName); + auto patch = YACReader::parseCVJSONComicInfo(result, volumeInfo); + patch.set(&DescriptiveComicMetadata::comicVineID, comicId); + setLoadingMessage(tr("Retrieving tags for: %1").arg(recordFileName(comics[currentIndex]))); + saveMetadata({ { comics[currentIndex], patch } }); if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) { finishSuccessfully(); @@ -510,23 +616,63 @@ void ComicVineDialog::toggleSkipButton() skipButton->setHidden(true); } -QString ComicVineDialog::volumeSearchStringFromComic(const ComicDB &comic) +QString ComicVineDialog::volumeSearchStringFromComic(const ComicRecord &comic) { - auto volume = comic.info.volume.toString().trimmed(); + const auto &info = recordMetadata(comic); + auto volume = info.volume.toString().trimmed(); if (!volume.isEmpty()) return volume; - auto series = comic.info.series.toString().trimmed(); + auto series = info.series.toString().trimmed(); if (!series.isEmpty()) return series; - auto alternateSeries = comic.info.alternateSeries.toString().trimmed(); + auto alternateSeries = info.alternateSeries.toString().trimmed(); if (!alternateSeries.isEmpty()) return alternateSeries; - // extract information from file name - auto parentFolderName = comic.getParentFolderName(); - return parentFolderName; + // the folder that holds the file, or held it for a deleted comic + return parentFolderName(recordPath(comic)); +} + +void ComicVineDialog::saveMetadata(const QList> &matches, const QList &missingIssues) +{ + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); + db.transaction(); + + // each comic_info or placeholder is written once + QSet written; + QSet writtenPlaceholders; + for (const auto &[comic, patch] : matches) { + if (const auto *placeholder = recordPlaceholder(comic)) { + if (writtenPlaceholders.contains(placeholder->id) || !DBHelper::loadRecord(comic, db)) + continue; + writtenPlaceholders.insert(placeholder->id); + DBHelper::updatePlaceholderMetadata(placeholder->id, patch, db); + continue; + } + const auto id = recordInfo(comic)->id; + if (written.contains(id) || !DBHelper::loadRecord(comic, db)) + continue; + written.insert(id); + DBHelper::updateComicInfoMetadata(id, patch, db); + } + + std::optional unusedCovers = QStringList(); + if (!missingIssues.isEmpty()) + unusedCovers = folderId ? DBHelper::placeIssuesInFolder(missingIssues, *folderId, db) : std::nullopt; + + if (unusedCovers && db.commit()) { + DBHelper::removePlaceholderCovers(databasePath, *unusedCovers); + } else { + db.rollback(); + removeCovers(missingIssues); + } + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); } void ComicVineDialog::goToNextComic() @@ -538,9 +684,9 @@ void ComicVineDialog::goToNextComic() currentIndex++; - ComicDB comic = comics[currentIndex]; + const auto &comic = comics[currentIndex]; - QString title = comic.getTitleOrFileName(); + QString title = titleOrFileName(recordMetadata(comic), recordFileName(comic)); titleHeader->setSubTitle(tr("comic %1 of %2 - %3").arg(currentIndex + 1).arg(comics.length()).arg(title)); showSearchSingleComic(volumeSearchStringFromComic(comic)); @@ -558,7 +704,8 @@ void ComicVineDialog::finishSuccessfully() // closes and emits accepted exactly once. QMetaObject::invokeMethod(this, [this]() { clearState(); - accept(); }, Qt::QueuedConnection); + accept(); + showFailures(); }, Qt::QueuedConnection); } void ComicVineDialog::showLoading(const QString &message) @@ -572,9 +719,10 @@ void ComicVineDialog::showLoading(const QString &message) closeButton->setVisible(true); } +// the work threads also report their progress void ComicVineDialog::setLoadingMessage(const QString &message) { - loadingMessage->setText(message); + QMetaObject::invokeMethod(loadingMessage, [this, message] { loadingMessage->setText(message); }); } void ComicVineDialog::search() diff --git a/YACReaderLibrary/comic_vine/comic_vine_dialog.h b/YACReaderLibrary/comic_vine/comic_vine_dialog.h index 547bbfc5b..80ae95a15 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_dialog.h +++ b/YACReaderLibrary/comic_vine/comic_vine_dialog.h @@ -1,12 +1,18 @@ #ifndef COMIC_VINE_DIALOG_H #define COMIC_VINE_DIALOG_H -#include "comic_db.h" +#include "comic_record.h" +#include "db_helper.h" +#include "descriptive_comic_metadata.h" +#include "library_ids.h" #include "themable.h" #include "volume_search_query.h" #include +#include +#include + class QPushButton; class QStackedWidget; class QLabel; @@ -33,12 +39,15 @@ class ComicVineDialog : public QDialog, protected Themable explicit ComicVineDialog(QWidget *parent = nullptr); QString databasePath; QString basePath; - void setComics(const QList &comics); + // the folder of the comics; only then the other issues of a volume can be added as missing comics + std::optional folderId; + void setComics(const QList &comics); QSize sizeHint() const override; QSize minimumSizeHint() const override; - void getComicsInfo(QList> matchingInfo, const SelectedVolumeInfo &volumeInfo); + void getComicsInfo(QList> matchingInfo, const QStringList &missingIssues, const SelectedVolumeInfo &volumeInfo, int runId); void getComicInfo(const QString &comicId, const SelectedVolumeInfo &volumeInfo); void closeEvent(QCloseEvent *event) override; + void done(int result) override; signals: public slots: @@ -70,7 +79,12 @@ protected slots: void clearState(); void finishSuccessfully(); void toggleSkipButton(); - QString volumeSearchStringFromComic(const ComicDB &comic); + QString volumeSearchStringFromComic(const ComicRecord &comic); + // writes each patch to the comic_info of its record when the record still exists, each comic_info once, and the + // missing issues to the folder, in one transaction + void saveMetadata(const QList> &matches, const QList &missingIssues = { }); + MissingIssue missingIssue(const QString &issueJson, const SelectedVolumeInfo &volumeInfo); + void removeCovers(const QList &missingIssues); enum class ScraperMode { SingleComic, // the scraper has been opened for a single comic @@ -117,7 +131,7 @@ protected slots: void doLoading(); void doConnections(); - QList comics; + QList comics; SeriesQuestion *seriesQuestionWidget; SearchSingleComic *searchSingleComicWidget; @@ -128,6 +142,17 @@ protected slots: VolumeSearchQuery currentVolumeSearchQuery; QString currentVolumeId; + // a closed or reopened dialog changes it, so the work of the previous run writes nothing + std::atomic_int run { 0 }; + + // what failed in this run, for the report at the end; after a limit answer no more details are asked + std::atomic_int failedComics { 0 }; + std::atomic_int basicMissingIssues { 0 }; + std::atomic_int missingIssuesWithoutCover { 0 }; + std::atomic_bool limitReached { false }; + // false when the detail request failed; also notes a limit answer + bool detailAnswered(bool error, bool timeout, const QByteArray &detail); + void showFailures(); protected: void applyTheme(const Theme &theme) override; diff --git a/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp b/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp index 1b0c93462..40e260223 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_json_parser.cpp @@ -5,6 +5,7 @@ #include "selected_volume_info.h" #include +#include #include QString getCharacters(const QVariant &json_characters); @@ -13,25 +14,38 @@ QPair getFirstStoryArcIdAndName(const QVariant &json_story_arc QPair getArcNumberAndArcCount(const QString &storyArcId, const QString &comicId); QList getNamesFromList(const QVariant &json_list); -ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, const SelectedVolumeInfo &volumeInfo) +YACReader::ComicVineAnswer YACReader::comicVineAnswer(bool error, bool timeout, const QByteArray &body) { + const auto status = QJsonDocument::fromJson(body).object().value("status_code").toInt(); + if (status == 107) + return ComicVineAnswer::LimitReached; + if (error || timeout || status != 1) + return ComicVineAnswer::Failed; + return ComicVineAnswer::Answered; +} + +DescriptiveMetadataPatch YACReader::parseCVJSONComicInfo(const QString &json, const SelectedVolumeInfo &volumeInfo) +{ + using M = DescriptiveComicMetadata; + DescriptiveMetadataPatch patch; + QJsonParseError Err; QVariantMap sc = QJsonDocument::fromJson(json.toUtf8(), &Err).toVariant().toMap(); if (Err.error != QJsonParseError::NoError) { qDebug("Error detected"); - return comic; + return patch; } int numResults = sc.value("number_of_total_results").toInt(); // fix to weird behaviour using hasNext if (numResults > 0) { QVariantMap result = sc.value("results").toMap(); - comic.info.title = result.value("name"); - comic.info.number = result.value("issue_number"); + patch.set(&M::title, result.value("name")); + patch.set(&M::number, result.value("issue_number")); // changed in 9.13, volume actually means series in ComicVine - comic.info.series = result.value("volume").toMap().value("name"); + patch.set(&M::series, result.value("volume").toMap().value("name")); if (result.contains("person_credits") && !result.value("person_credits").isNull()) { auto authors = getAuthors(result.value("person_credits")); @@ -43,12 +57,12 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, con QString letterer = authors.values("letterer").join("\n"); QString coverArtist = authors.values("cover").join("\n"); - comic.info.writer = writer; - comic.info.penciller = penciller; - comic.info.inker = inker; - comic.info.colorist = colorist; - comic.info.letterer = letterer; - comic.info.coverArtist = coverArtist; + patch.set(&M::writer, writer); + patch.set(&M::penciller, penciller); + patch.set(&M::inker, inker); + patch.set(&M::colorist, colorist); + patch.set(&M::letterer, letterer); + patch.set(&M::coverArtist, coverArtist); } if (result.contains("cover_date") && !result.value("cover_date").isNull()) { @@ -58,20 +72,20 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, con if (tempList.length() == 3) { std::reverse(tempList.begin(), tempList.end()); - comic.info.date = tempList.join("/"); + patch.set(&M::date, tempList.join("/")); } } if (result.contains("description") && !result.value("description").isNull() && !result.value("description").toString().trimmed().isEmpty()) { - comic.info.synopsis = result.value("description"); + patch.set(&M::synopsis, result.value("description")); } else if (result.contains("deck") && !result.value("deck").isNull() && !result.value("deck").toString().trimmed().isEmpty()) { - comic.info.synopsis = result.value("deck"); + patch.set(&M::synopsis, result.value("deck")); } else if (!volumeInfo.description.trimmed().isEmpty() && volumeInfo.numIssues < 2) { - comic.info.synopsis = volumeInfo.description.trimmed(); + patch.set(&M::synopsis, volumeInfo.description.trimmed()); } if (result.contains("character_credits") && !result.value("character_credits").isNull()) { - comic.info.characters = getCharacters(result.value("character_credits")); + patch.set(&M::characters, getCharacters(result.value("character_credits"))); } if (result.contains("story_arc_credits") && !result.value("story_arc_credits").isNull()) { @@ -87,33 +101,31 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, con QString arcNumber = arcNumberAndArcCount.first; QString arcCount = arcNumberAndArcCount.second; - comic.info.storyArc = storyArcName; - comic.info.arcNumber = arcNumber; - comic.info.arcCount = arcCount; + patch.set(&M::storyArc, storyArcName); + patch.set(&M::arcNumber, arcNumber); + patch.set(&M::arcCount, arcCount); } } } if (result.contains("location_credits") && !result.value("location_credits").isNull()) { - comic.info.locations = getNamesFromList(result.value("location_credits")).join("\n"); + patch.set(&M::locations, getNamesFromList(result.value("location_credits")).join("\n")); } if (result.contains("team_credits") && !result.value("team_credits").isNull()) { - comic.info.teams = getNamesFromList(result.value("team_credits")).join("\n"); + patch.set(&M::teams, getNamesFromList(result.value("team_credits")).join("\n")); } if (result.contains("character_credits") && !result.value("character_credits").isNull()) { - comic.info.characters = getNamesFromList(result.value("character_credits")).join("\n"); + patch.set(&M::characters, getNamesFromList(result.value("character_credits")).join("\n")); } - comic.info.count = volumeInfo.numIssues; - - comic.info.publisher = volumeInfo.publisher; + patch.set(&M::count, volumeInfo.numIssues); - comic.info.edited = true; + patch.set(&M::publisher, volumeInfo.publisher); } - return comic; + return patch; } QString getCharacters(const QVariant &json_characters) diff --git a/YACReaderLibrary/comic_vine/comic_vine_json_parser.h b/YACReaderLibrary/comic_vine/comic_vine_json_parser.h index 8f395759e..cbe71b843 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_json_parser.h +++ b/YACReaderLibrary/comic_vine/comic_vine_json_parser.h @@ -8,7 +8,17 @@ struct SelectedVolumeInfo; namespace YACReader { -ComicDB parseCVJSONComicInfo(ComicDB &comic, const QString &json, const SelectedVolumeInfo &volumeInfo); +// the descriptive fields that Comic Vine returned; the other fields keep their values +DescriptiveMetadataPatch parseCVJSONComicInfo(const QString &json, const SelectedVolumeInfo &volumeInfo); + +enum class ComicVineAnswer { + Answered, + Failed, + // Comic Vine does not document its limit answer; clients report status code 107 + LimitReached, +}; +// a request fails when the network fails or Comic Vine gives a status other than 1 +ComicVineAnswer comicVineAnswer(bool error, bool timeout, const QByteArray &body); } diff --git a/YACReaderLibrary/comic_vine/model/local_comic_list_model.cpp b/YACReaderLibrary/comic_vine/model/local_comic_list_model.cpp index e2cbc2bac..9d3d995f2 100644 --- a/YACReaderLibrary/comic_vine/model/local_comic_list_model.cpp +++ b/YACReaderLibrary/comic_vine/model/local_comic_list_model.cpp @@ -5,9 +5,11 @@ LocalComicListModel::LocalComicListModel(QObject *parent) { } -void LocalComicListModel::load(QList &comics) +void LocalComicListModel::load(const QList &comics) { - _data = comics; + _data.clear(); + for (const auto &comic : comics) + _data.append(comic); } QModelIndex LocalComicListModel::parent(const QModelIndex &index) const @@ -49,7 +51,7 @@ QVariant LocalComicListModel::data(const QModelIndex &index, int role) const int row = index.row(); // if(row < _data.count()) - return _data[row].getFileName(); + return _data[row] ? recordFileName(*_data[row]) : QString(); // else // return QVariant(); } @@ -83,7 +85,7 @@ QModelIndex LocalComicListModel::index(int row, int column, const QModelIndex &p return createIndex(row, column); } -QList LocalComicListModel::getData() +QList> LocalComicListModel::getData() { return _data; } @@ -106,7 +108,7 @@ void LocalComicListModel::removeComics(const QList &selectedIndexes beginInsertRows(QModelIndex(), _data.count() - _removed.count(), _data.count() - 1); for (int i = 0; i < _removed.count(); i++) - _data.append(ComicDB()); + _data.append(std::nullopt); endInsertRows(); } @@ -114,7 +116,7 @@ void LocalComicListModel::restoreAll() { int numItemsToRemove = 0; for (int i = 0; numItemsToRemove < _removed.count(); i++) { - if (_data.at(i).getFileName().isEmpty()) { + if (!_data.at(i)) { beginRemoveRows(QModelIndex(), i, i); _data.removeAt(i); endRemoveRows(); @@ -172,5 +174,5 @@ void LocalComicListModel::addExtraRows(int numRows) { numExtraRows = numRows; for (int i = 0; i < numExtraRows; i++) - _data.append(ComicDB()); + _data.append(std::nullopt); } diff --git a/YACReaderLibrary/comic_vine/model/local_comic_list_model.h b/YACReaderLibrary/comic_vine/model/local_comic_list_model.h index 620a47bf6..9b57cb2aa 100644 --- a/YACReaderLibrary/comic_vine/model/local_comic_list_model.h +++ b/YACReaderLibrary/comic_vine/model/local_comic_list_model.h @@ -1,17 +1,19 @@ #ifndef LOCAL_COMIC_LIST_MODEL_H #define LOCAL_COMIC_LIST_MODEL_H -#include "comic_db.h" +#include "comic_record.h" #include +#include + class LocalComicListModel : public QAbstractItemModel { Q_OBJECT public: explicit LocalComicListModel(QObject *parent = nullptr); - void load(QList &comics); + void load(const QList &comics); // QAbstractItemModel methods QModelIndex parent(const QModelIndex &index) const override; @@ -22,7 +24,8 @@ class LocalComicListModel : public QAbstractItemModel QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QList getData(); + // a padding row (no comic) has no record + QList> getData(); void removeComics(const QList &selectedIndexes); void restoreAll(); @@ -35,8 +38,8 @@ public slots: private: int numExtraRows; - QList _data; - QList _removed; + QList> _data; + QList> _removed; }; #endif // LOCAL_COMIC_LIST_MODEL_H diff --git a/YACReaderLibrary/comic_vine/model/volume_comics_model.cpp b/YACReaderLibrary/comic_vine/model/volume_comics_model.cpp index 579b6a642..bfcebc1b7 100644 --- a/YACReaderLibrary/comic_vine/model/volume_comics_model.cpp +++ b/YACReaderLibrary/comic_vine/model/volume_comics_model.cpp @@ -38,7 +38,7 @@ void VolumeComicsModel::load(const QString &json) QString coverURL = resultsValue.value("image").toMap().value("medium_url").toString(); QString id = resultsValue.value("id").toString(); QStringList l; - l << issueNumber << name << coverURL << id; + l << issueNumber << name << coverURL << id << QString::fromUtf8(QJsonDocument::fromVariant(resultsValue).toJson(QJsonDocument::Compact)); _data.push_back(l); } @@ -158,6 +158,18 @@ QString VolumeComicsModel::getComicId(int row) const return _data[row][ID]; } +int VolumeComicsModel::issueCount() const +{ + return _data.count(); +} + +QString VolumeComicsModel::getIssueJson(int row) const +{ + if (row >= _data.count()) + return ""; + return _data[row][ISSUE_JSON]; +} + QString VolumeComicsModel::getCoverURL(const QModelIndex &index) const { return _data[index.row()][COVER_URL]; diff --git a/YACReaderLibrary/comic_vine/model/volume_comics_model.h b/YACReaderLibrary/comic_vine/model/volume_comics_model.h index f9c7baa8f..4fc73a706 100644 --- a/YACReaderLibrary/comic_vine/model/volume_comics_model.h +++ b/YACReaderLibrary/comic_vine/model/volume_comics_model.h @@ -23,6 +23,10 @@ class VolumeComicsModel : public JSONModel QString getComicId(const QModelIndex &index) const; QString getComicId(int row) const; QString getCoverURL(const QModelIndex &index) const; + // without the extra rows + int issueCount() const; + // the issue as Comic Vine gave it in the list of the volume + QString getIssueJson(int row) const; signals: public slots: @@ -36,7 +40,8 @@ public slots: ISSUE = 0, TITLE, COVER_URL, - ID + ID, + ISSUE_JSON }; }; diff --git a/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp b/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp index cfffdecb7..b0e99a5df 100644 --- a/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp +++ b/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp @@ -17,7 +17,7 @@ ScraperResultsPaginator::ScraperResultsPaginator(QWidget *parent) connect(nextPage, &QAbstractButton::clicked, this, &ScraperResultsPaginator::loadNextPage); connect(previousPage, &QAbstractButton::clicked, this, &ScraperResultsPaginator::loadPreviousPage); - numElements = new QLabel(tr("Number of volumes found : %1")); + numElements = new QLabel(tr("Number of volumes found: %1")); numPages = new QLabel(tr("page %1 of %2")); pagesButtonsLayout->addSpacing(15); @@ -41,7 +41,7 @@ void ScraperResultsPaginator::update(const QString &json) rp.loadJSONResponse(json); currentPage = rp.getCurrentPage(); - numElements->setText(tr("Number of %1 found : %2").arg(customLabel).arg(rp.getNumResults())); + numElements->setText(tr("Number of %1 found: %2").arg(customLabel).arg(rp.getNumResults())); numPages->setText(tr("page %1 of %2").arg(currentPage).arg(rp.getTotalPages())); previousPage->setDisabled(currentPage == 1); diff --git a/YACReaderLibrary/comic_vine/select_comic.cpp b/YACReaderLibrary/comic_vine/select_comic.cpp index 67dd1ac2e..d71878ba6 100644 --- a/YACReaderLibrary/comic_vine/select_comic.cpp +++ b/YACReaderLibrary/comic_vine/select_comic.cpp @@ -13,7 +13,7 @@ SelectComic::SelectComic(QWidget *parent) : QWidget(parent), model(0) { - label = new QLabel(tr("Please, select the right comic info.")); + label = new QLabel(tr("Please select the right comic info.")); auto l = new QVBoxLayout; QWidget *leftWidget = new QWidget; diff --git a/YACReaderLibrary/comic_vine/select_volume.cpp b/YACReaderLibrary/comic_vine/select_volume.cpp index 2e778f323..429719f3c 100644 --- a/YACReaderLibrary/comic_vine/select_volume.cpp +++ b/YACReaderLibrary/comic_vine/select_volume.cpp @@ -28,7 +28,7 @@ SelectVolume::SelectVolume(QWidget *parent) proxyModel = new QSortFilterProxyModel; proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - label = new QLabel(tr("Please, select the right series for your comic.")); + label = new QLabel(tr("Please select the right series for your comic.")); auto l = new QVBoxLayout; QWidget *leftWidget = new QWidget; diff --git a/YACReaderLibrary/comic_vine/series_question.cpp b/YACReaderLibrary/comic_vine/series_question.cpp index 31e0ede21..d6747d8d2 100644 --- a/YACReaderLibrary/comic_vine/series_question.cpp +++ b/YACReaderLibrary/comic_vine/series_question.cpp @@ -9,7 +9,7 @@ SeriesQuestion::SeriesQuestion(QWidget *parent) { auto l = new QVBoxLayout; - questionLabel = new QLabel(tr("You are trying to get information for various comics at once, are they part of the same series?")); + questionLabel = new QLabel(tr("You are trying to get information for various comics at once. Are they part of the same series?")); yes = new QRadioButton(tr("yes")); no = new QRadioButton(tr("no")); diff --git a/YACReaderLibrary/comic_vine/sort_volume_comics.cpp b/YACReaderLibrary/comic_vine/sort_volume_comics.cpp index c6823bd75..1c87ce48e 100644 --- a/YACReaderLibrary/comic_vine/sort_volume_comics.cpp +++ b/YACReaderLibrary/comic_vine/sort_volume_comics.cpp @@ -1,6 +1,7 @@ #include "sort_volume_comics.h" #include "local_comic_list_model.h" +#include "scraper_checkbox.h" #include "scraper_tableview.h" #include "theme_manager.h" #include "volume_comics_model.h" @@ -56,7 +57,7 @@ void ScrapperToolButton::applyTheme(const Theme &theme) SortVolumeComics::SortVolumeComics(QWidget *parent) : QWidget(parent) { - label = new QLabel(tr("Please, sort the list of comics on the left until it matches the comics' information.")); + label = new QLabel(tr("Please sort the list of comics on the left until it matches the comics' information.")); sortLabel = new QLabel(tr("sort comics to match comic information")); @@ -113,10 +114,15 @@ SortVolumeComics::SortVolumeComics(QWidget *parent) content->setRowStretch(0, 1); + missingIssuesCheck = new ScraperCheckBox(QString()); + missingIssuesCheck->hide(); + l->addSpacing(15); l->addWidget(label, 0); l->addSpacing(5); l->addLayout(content, 1); + l->addSpacing(10); + l->addWidget(missingIssuesCheck); l->setContentsMargins(0, 0, 0, 0); setLayout(l); @@ -139,7 +145,7 @@ SortVolumeComics::SortVolumeComics(QWidget *parent) initTheme(this); } -void SortVolumeComics::setData(QList &comics, const QString &json, const QString &vID) +void SortVolumeComics::setData(const QList &comics, const QString &json, const QString &vID) { // set up models localComicsModel = new LocalComicListModel; @@ -161,6 +167,12 @@ void SortVolumeComics::setData(QList &comics, const QString &json, cons tableVolumeComics->resizeColumnToContents(0); + connect(localComicsModel, &QAbstractItemModel::rowsMoved, this, &SortVolumeComics::updateMissingIssues); + connect(localComicsModel, &QAbstractItemModel::rowsRemoved, this, &SortVolumeComics::updateMissingIssues); + connect(localComicsModel, &QAbstractItemModel::rowsInserted, this, &SortVolumeComics::updateMissingIssues); + missingIssuesCheck->setChecked(false); + updateMissingIssues(); + currentVolumeId = vID; paginator->update(json); } @@ -245,19 +257,48 @@ void SortVolumeComics::showRemovedComicsSelector() { } -QList> SortVolumeComics::getMatchingInfo() +void SortVolumeComics::setMissingIssuesOption(bool visible) +{ + missingIssuesCheck->setVisible(visible); +} + +bool SortVolumeComics::addsMissingIssues() const +{ + // the page is hidden when the dialog asks + return !missingIssuesCheck->isHidden() && missingIssuesCheck->isEnabled() && missingIssuesCheck->isChecked(); +} + +QStringList SortVolumeComics::getMissingIssues() +{ + const auto comicList = localComicsModel->getData(); + QStringList issues; + for (int row = 0; row < volumeComicsModel->issueCount(); row++) { + if (row >= comicList.size() || !comicList.at(row)) + issues.append(volumeComicsModel->getIssueJson(row)); + } + return issues; +} + +void SortVolumeComics::updateMissingIssues() +{ + const auto count = getMissingIssues().size(); + missingIssuesCheck->setText(tr("Add the %n other issue(s) of this volume as missing comics", "", count)); + missingIssuesCheck->setEnabled(count > 0); +} + +QList> SortVolumeComics::getMatchingInfo() { const auto comicList = localComicsModel->getData(); - QList> l; + QList> l; int index = 0; QString id; for (const auto &c : comicList) { id = volumeComicsModel->getComicId(index); - if (!c.getFileName().isEmpty() && !id.isEmpty()) // there is a valid comic, and valid comic ID + if (c && !id.isEmpty()) // there is a valid comic, and valid comic ID { - l.push_back(QPair(c, id)); + l.push_back(QPair(*c, id)); } index++; } diff --git a/YACReaderLibrary/comic_vine/sort_volume_comics.h b/YACReaderLibrary/comic_vine/sort_volume_comics.h index c57248687..1e3ca8a4a 100644 --- a/YACReaderLibrary/comic_vine/sort_volume_comics.h +++ b/YACReaderLibrary/comic_vine/sort_volume_comics.h @@ -1,7 +1,7 @@ #ifndef SORT_VOLUME_COMICS_H #define SORT_VOLUME_COMICS_H -#include "comic_db.h" +#include "comic_record.h" #include "scraper_results_paginator.h" #include "themable.h" @@ -9,6 +9,7 @@ #include #include +class ScraperCheckBox; class ScraperTableView; class LocalComicListModel; class VolumeComicsModel; @@ -45,8 +46,13 @@ class SortVolumeComics : public QWidget, protected Themable explicit SortVolumeComics(QWidget *parent = nullptr); public slots: - void setData(QList &comics, const QString &json, const QString &vID); - QList> getMatchingInfo(); + void setData(const QList &comics, const QString &json, const QString &vID); + QList> getMatchingInfo(); + // the check box that adds the other issues of the volume as missing comics; it starts unchecked + void setMissingIssuesOption(bool visible); + bool addsMissingIssues() const; + // the issues of the volume with no comic next to them, as Comic Vine gave them + QStringList getMissingIssues(); protected slots: void synchronizeScroll(int pos); @@ -64,6 +70,7 @@ protected slots: private slots: void loadNextPage(); + void updateMissingIssues(); void loadPreviousPage(); private: @@ -71,6 +78,7 @@ private slots: QLabel *sortLabel; ScraperTableView *tableFiles; ScraperTableView *tableVolumeComics; + ScraperCheckBox *missingIssuesCheck; LocalComicListModel *localComicsModel; VolumeComicsModel *volumeComicsModel; diff --git a/YACReaderLibrary/comics_view.cpp b/YACReaderLibrary/comics_view.cpp index f053e3498..0d78df58b 100644 --- a/YACReaderLibrary/comics_view.cpp +++ b/YACReaderLibrary/comics_view.cpp @@ -4,6 +4,7 @@ #include "comic.h" #include "comic_db.h" #include "comic_files_manager.h" +#include "comic_info_object.h" #include #include @@ -12,11 +13,10 @@ #include ComicsView::ComicsView(QWidget *parent) - : QWidget(parent), model(nullptr), comicDB(nullptr) + : QWidget(parent), model(nullptr), comicInfo(nullptr) { qmlRegisterType("com.yacreader.ComicModel", 1, 0, "ComicModel"); - qmlRegisterType("com.yacreader.ComicDB", 1, 0, "ComicDB"); - qmlRegisterType("com.yacreader.ComicInfo", 1, 0, "ComicInfo"); + qmlRegisterType("com.yacreader.ComicInfo", 1, 0, "ComicInfo"); // QML renders into its own native window. A QQuickWidget would make Qt compose the whole // library window through RHI and copy its full backing store on every repaint (QTBUG-120565), @@ -37,11 +37,10 @@ ComicsView::ComicsView(QWidget *parent) container->setFocusPolicy(Qt::StrongFocus); view->installEventFilter(this); - comicDB = new ComicDB(); - auto comicInfo = &(comicDB->info); + comicInfo = new ComicInfoObject(); QQmlContext *ctxt = view->rootContext(); - ctxt->setContextProperty("comic", comicDB); + ctxt->setContextProperty("comic", comicInfo); ctxt->setContextProperty("comicInfo", comicInfo); ctxt->setContextProperty("comic_info_index", 0); @@ -87,10 +86,10 @@ void ComicsView::updateInfoForIndex(int index) { QQmlContext *ctxt = view->rootContext(); - // Clear the member before destroying the object. Deleting ComicDB notifies + // Clear the member before destroying the object. Deleting it notifies // QML and can re-enter this method; an invalid index must also not leave a // dangling pointer that is deleted again when the next comic is selected. - delete std::exchange(comicDB, nullptr); + delete std::exchange(comicInfo, nullptr); if ((index < 0) || (index >= model->rowCount())) { ctxt->setContextProperty("comic", nullptr); @@ -99,11 +98,10 @@ void ComicsView::updateInfoForIndex(int index) return; } - comicDB = new ComicDB(model->getComic(this->model->index(index, 0))); - ComicInfo *comicInfo = &(comicDB->info); - comicInfo->isFavorite = model->isFavorite(model->index(index, 0)); + const auto modelIndex = model->index(index, 0); + comicInfo = new ComicInfoObject(model->record(modelIndex), model->isFavorite(modelIndex)); - ctxt->setContextProperty("comic", comicDB); + ctxt->setContextProperty("comic", comicInfo); ctxt->setContextProperty("comicInfo", comicInfo); ctxt->setContextProperty("comic_info_index", index); diff --git a/YACReaderLibrary/comics_view.h b/YACReaderLibrary/comics_view.h index 9675ac730..dfcfc6b8e 100644 --- a/YACReaderLibrary/comics_view.h +++ b/YACReaderLibrary/comics_view.h @@ -13,6 +13,7 @@ class QSplitter; class ComicFlowWidget; class QToolBar; class ComicModel; +class ComicInfoObject; class QQuickView; class ComicsView : public QWidget @@ -52,6 +53,7 @@ public slots: signals: void selected(unsigned int); + void comicSelectionChanged(); void openComic(const ComicDB &comic, const ComicModel::Mode mode); void comicRated(int, QModelIndex); @@ -62,7 +64,7 @@ public slots: // Drops void copyComicsToCurrentFolder(QList>); void moveComicsToCurrentFolder(QList>); - void customComicCoverRequested(qulonglong comicId, const QString &imagePath); + void customComicCoverRequested(const ComicRowId &row, const QString &imagePath); void customFolderCoverRequested(qulonglong folderId, const QString &imagePath); protected: @@ -78,7 +80,7 @@ public slots: QQuickView *view; QWidget *container; - ComicDB *comicDB; + ComicInfoObject *comicInfo; private: }; diff --git a/YACReaderLibrary/content_view_state.h b/YACReaderLibrary/content_view_state.h index 399262aed..c1f081cac 100644 --- a/YACReaderLibrary/content_view_state.h +++ b/YACReaderLibrary/content_view_state.h @@ -1,6 +1,8 @@ #ifndef CONTENT_VIEW_STATE_H #define CONTENT_VIEW_STATE_H +#include "comic_item.h" + #include struct ContentItemRef { @@ -12,7 +14,8 @@ struct ContentItemRef { }; Kind kind = None; - qulonglong id = 0; + qulonglong folderId = 0; + ComicRowId comic; }; struct ContentViewState { diff --git a/YACReaderLibrary/create_library_dialog.cpp b/YACReaderLibrary/create_library_dialog.cpp index 5c3a12788..5c6ac517f 100644 --- a/YACReaderLibrary/create_library_dialog.cpp +++ b/YACReaderLibrary/create_library_dialog.cpp @@ -18,12 +18,12 @@ CreateLibraryDialog::CreateLibraryDialog(QWidget *parent) void CreateLibraryDialog::setupUI() { - textLabel = new QLabel(tr("Comics folder : ")); + textLabel = new QLabel(tr("Comics folder: ")); path = new QLineEdit; textLabel->setBuddy(path); connect(path, &QLineEdit::textChanged, this, &CreateLibraryDialog::pathSetted); - nameLabel = new QLabel(tr("Library Name : ")); + nameLabel = new QLabel(tr("Library Name: ")); nameEdit = new QLineEdit; nameLabel->setBuddy(nameEdit); connect(nameEdit, &QLineEdit::textChanged, this, &CreateLibraryDialog::nameSetted); @@ -54,7 +54,7 @@ void CreateLibraryDialog::setupUI() content->setColumnMinimumWidth(2, 0); // TODO auto bottomLayout = new QHBoxLayout; - bottomLayout->addWidget(message = new QLabel(tr("Create a library could take several minutes. You can stop the process and update the library later for completing the task."))); + bottomLayout->addWidget(message = new QLabel(tr("Creating a library could take several minutes. You can stop the process and update the library later to complete the task."))); message->setWordWrap(true); // message->hide(); bottomLayout->addStretch(); @@ -162,7 +162,7 @@ UpdateLibraryDialog::UpdateLibraryDialog(QWidget *parent) : QDialog(parent) { auto mainLayout = new QVBoxLayout; - mainLayout->addWidget(message = new QLabel(tr("Updating...."))); + mainLayout->addWidget(message = new QLabel(tr("Updating..."))); mainLayout->addWidget(currentFileLabel = new QLabel("\n\n\n\n")); currentFileLabel->setWordWrap(true); diff --git a/YACReaderLibrary/current_comic_view_helper.cpp b/YACReaderLibrary/current_comic_view_helper.cpp index 025502945..42a3bc09a 100644 --- a/YACReaderLibrary/current_comic_view_helper.cpp +++ b/YACReaderLibrary/current_comic_view_helper.cpp @@ -4,13 +4,9 @@ ComicDB currentComicFromModel(ComicModel *model, bool &found) { - const auto comics = model->getAllComics(); - - for (const auto &comic : comics) { - if (comic.info.read == false) { - found = true; - return comic; - } + if (auto comic = model->firstUnreadComic()) { + found = true; + return *comic; } found = false; diff --git a/YACReaderLibrary/db/comic_item.cpp b/YACReaderLibrary/db/comic_item.cpp index d7abfd6ec..c647f0d71 100644 --- a/YACReaderLibrary/db/comic_item.cpp +++ b/YACReaderLibrary/db/comic_item.cpp @@ -1,55 +1,266 @@ - #include "comic_item.h" -#include +#include "db_helper.h" +#include "overloaded.h" +#include "qnaturalsorting.h" +#include "search_query.h" +#include "smart_list.h" + +#include +#include +#include + +#include +#include +#include +#include + +const DescriptiveComicMetadata &rowMetadata(const ComicItem &item) +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &row) -> const DescriptiveComicMetadata & { return row.comic.info; }, + [](const ComicItemCase::ComicInList &row) -> const DescriptiveComicMetadata & { return row.comic.info; }, + [](const ComicItemCase::DeletedComicInList &row) -> const DescriptiveComicMetadata & { return row.info; }, + [](const ComicItemCase::PlaceholderInList &row) -> const DescriptiveComicMetadata & { return row.placeholder; }, + [](const ComicItemCase::PlaceholderInFolder &row) -> const DescriptiveComicMetadata & { return row.placeholder; }, + }, + item); +} + +const ComicDB *rowComic(const ComicItem &item) +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &row) -> const ComicDB * { return &row.comic; }, + [](const ComicItemCase::ComicInList &row) -> const ComicDB * { return &row.comic; }, + [](const ComicItemCase::DeletedComicInList &) -> const ComicDB * { return nullptr; }, + [](const ComicItemCase::PlaceholderInList &) -> const ComicDB * { return nullptr; }, + [](const ComicItemCase::PlaceholderInFolder &) -> const ComicDB * { return nullptr; }, + }, + item); +} + +QString rowFileName(const ComicItem &item) +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &row) { return row.comic.file.fileName; }, + [](const ComicItemCase::ComicInList &row) { return row.comic.file.fileName; }, + [](const ComicItemCase::DeletedComicInList &row) { return row.entry.lastKnownFile ? row.entry.lastKnownFile->name : QString(); }, + [](const ComicItemCase::PlaceholderInList &) { return QString(); }, + [](const ComicItemCase::PlaceholderInFolder &) { return QString(); }, + }, + item); +} + +namespace { + +int sign(int order) +{ + return order < 0 ? -1 : (order > 0 ? 1 : 0); +} + +// -1, 0, or 1 in natural order without case; an empty text is first when `emptyFirst`, else last +int compareNatural(const QString &a, const QString &b, bool emptyFirst) +{ + if (a.isEmpty() != b.isEmpty()) + return a.isEmpty() == emptyFirst ? -1 : 1; + return sign(naturalCompare(a, b, Qt::CaseInsensitive)); +} + +// the order of comicNumberLessThan for two numbers without file names: numbered first +int compareNumbers(const std::optional &a, const std::optional &b) +{ + if (a && b) + return sign(naturalCompare(*a, *b, Qt::CaseInsensitive)); + if (a.has_value() == b.has_value()) + return 0; + return a ? -1 : 1; +} + +struct SearchSortKey { + QString series; + QString volume; + std::optional number; + QString fileName; + QString path; +}; + +SearchSortKey searchSortKey(const ComicItem &item) +{ + const auto &metadata = rowMetadata(item); + const auto *comic = rowComic(item); + return { + metadata.series.toString().trimmed(), + metadata.volume.toString().trimmed(), + metadata.number.isNull() ? std::nullopt : std::optional(metadata.number.toString()), + rowFileName(item), + comic != nullptr ? comic->file.path : QString(), + }; +} + +int compareSearchSortKeys(const SearchSortKey &a, const SearchSortKey &b) +{ + if (const auto order = compareNatural(a.series, b.series, false); order != 0) + return order; + if (const auto order = compareNatural(a.volume, b.volume, true); order != 0) + return order; + if (const auto order = compareNumbers(a.number, b.number); order != 0) + return order; + if (const auto order = compareNatural(a.fileName, b.fileName, true); order != 0) + return order; + return compareNatural(a.path, b.path, true); +} + +} + +namespace ComicItems { + +QList fromQuery(QSqlQuery &query) +{ + QList items; + const auto indexes = ComicDB::columnIndexes(query.record(), "c", "ci"); + while (query.next()) { + items.append(ComicItemCase::Comic { ComicDB::fromQuery(query, indexes) }); + } + return items; +} + +QList fromQuery(QSqlQuery &query, ListKind listKind) +{ + QList items; + const auto record = query.record(); + const auto indexes = ComicDB::columnIndexes(record, "c", "ci"); + const auto entryIndexes = ListEntry::columnIndexes(record, "e", listKind); + const auto placeholderIndexes = Placeholder::columnIndexes(record, "p"); + const auto fileId = indexes.file.first(); + const auto placeholderId = placeholderIndexes.first(); + while (query.next()) { + auto entry = ListEntry::fromQuery(query, entryIndexes, listKind); + if (!query.value(placeholderId).isNull()) + items.append(ComicItemCase::PlaceholderInList { Placeholder::fromQuery(query, placeholderIndexes), std::move(entry) }); + else if (query.value(fileId).isNull()) + items.append(ComicItemCase::DeletedComicInList { ComicInfo::fromQuery(query, indexes.info), std::move(entry) }); + else + items.append(ComicItemCase::ComicInList { ComicDB::fromQuery(query, indexes), std::move(entry) }); + } + return items; +} + +QList loadFolder(FolderId folderId, QSqlDatabase &db) +{ + QSqlQuery selectQuery(db); + selectQuery.setForwardOnly(true); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " " + "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " + "WHERE c.parentId = :parentId"); + selectQuery.bindValue(":parentId", folderId.value()); + selectQuery.exec(); -//! [0] -ComicItem::ComicItem(const QList &data) + auto items = fromQuery(selectQuery); + if (db.isOpen()) { + for (auto &placeholder : DBHelper::loadFolderPlaceholders(folderId, db).value_or(QList())) + items.append(ComicItemCase::PlaceholderInFolder { std::move(placeholder), folderId }); + } + std::sort(items.begin(), items.end(), folderLessThan); + return items; +} +QList loadLabel(LabelId labelId, QSqlDatabase &db) { - itemData = data; + QSqlQuery selectQuery(db); + selectQuery.setForwardOnly(true); + selectQuery.prepare(DBHelper::listEntriesQuery(ListKind::Label)); + selectQuery.bindValue(":list_id", labelId.value()); + selectQuery.exec(); + return fromQuery(selectQuery, ListKind::Label); } -//! [0] -//! [1] -ComicItem::~ComicItem() +QList loadReadingList(ReadingListId readingListId, QSqlDatabase &db, bool *hasChildLists) { + QList ids; + ids << readingListId.value(); + + QSqlQuery subfolders(db); + subfolders.prepare("SELECT id " + "FROM reading_list " + "WHERE parentId = :parentId " + "ORDER BY ordering ASC"); + subfolders.bindValue(":parentId", readingListId.value()); + subfolders.exec(); + while (subfolders.next()) + ids << subfolders.record().value(0).toULongLong(); + + if (hasChildLists != nullptr) + *hasChildLists = ids.length() > 1; + + QList items; + QSqlQuery selectQuery(db); + selectQuery.setForwardOnly(true); + selectQuery.prepare(DBHelper::listEntriesQuery(ListKind::ReadingList)); + for (const auto id : std::as_const(ids)) { + selectQuery.bindValue(":list_id", id); + selectQuery.exec(); + + items << fromQuery(selectQuery, ListKind::ReadingList); + } + return items; } -//! [1] -//! [5] -int ComicItem::columnCount() const +std::optional> loadSmartList(SmartListId smartListId, QSqlDatabase &db) { - return itemData.count(); + const auto list = DBHelper::loadSmartList(smartListId, db); + if (!list) + return std::nullopt; + try { + auto query = allComicsSearchQuery(db, list->query); + return sortedForSearch(fromQuery(query)); + } catch (const std::exception &) { + return QList(); + } } -QList ComicItem::getData() const +QList loadRecent(qint64 days, QSqlDatabase &db) { - return itemData; + static const QString sql = "SELECT " + ComicDB::selectList("c", "ci") + " FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE ci.added > :limit ORDER BY ci.added DESC"; + QSqlQuery query(db); + query.setForwardOnly(true); + query.prepare(sql); + query.bindValue(":limit", QDateTime::currentDateTime().addDays(-days).toSecsSinceEpoch()); + query.exec(); + return fromQuery(query); } -void ComicItem::setData(const QList &data) +bool folderLessThan(const ComicItem &c1, const ComicItem &c2) { - itemData = data; + return comicNumberLessThan(rowMetadata(c1).number, rowFileName(c1), rowMetadata(c2).number, rowFileName(c2)); } -//! [5] -//! [6] -QVariant ComicItem::data(int column) const +bool searchLessThan(const ComicItem &c1, const ComicItem &c2) { - return itemData.value(column); + return compareSearchSortKeys(searchSortKey(c1), searchSortKey(c2)) < 0; } -//! [6] -void ComicItem::setData(int column, const QVariant &value) +QList sortedForFolder(QList items) { - itemData[column] = value; + std::sort(items.begin(), items.end(), folderLessThan); + return items; } -//! [8] -int ComicItem::row() const +QList sortedForSearch(QList items) { + QList keys; + keys.reserve(items.size()); + for (const auto &item : std::as_const(items)) + keys.append(searchSortKey(item)); + + QList order(items.size()); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), [&keys](qsizetype a, qsizetype b) { return compareSearchSortKeys(keys.at(a), keys.at(b)) < 0; }); + + QList sorted; + sorted.reserve(items.size()); + for (const auto row : std::as_const(order)) + sorted.append(std::move(items[row])); + return sorted; +} - return 0; } -//! [8] diff --git a/YACReaderLibrary/db/comic_item.h b/YACReaderLibrary/db/comic_item.h index 24b3da190..06bfb371a 100644 --- a/YACReaderLibrary/db/comic_item.h +++ b/YACReaderLibrary/db/comic_item.h @@ -1,27 +1,104 @@ -#ifndef TABLEITEM_H -#define TABLEITEM_H +#ifndef COMIC_ITEM_H +#define COMIC_ITEM_H + +#include "comic_db.h" +#include "list_entry.h" +#include "placeholder.h" #include -#include - -//! [0] -class ComicItem : public QObject -{ - Q_OBJECT -public: - ComicItem(const QList &data); - ~ComicItem() override; - int columnCount() const; - QList getData() const; - void setData(const QList &data); - QVariant data(int column) const; - void setData(int column, const QVariant &value); - int row() const; - // unsigned long long int id; //TODO sustituir por una clase adecuada - // Comic comic; -private: - QList itemData; + +#include +#include + +class QSqlDatabase; +class QSqlQuery; + +// `Comic` is also the class that reads comic files, so the cases live in their own namespace. +namespace ComicItemCase { + +struct Comic { + ComicDB comic; +}; + +struct ComicInList { + ComicDB comic; + ListEntry entry; +}; + +// an entry whose comic_info has no file in the library +struct DeletedComicInList { + ComicInfo info; + ListEntry entry; +}; + +// an entry of an expected issue that has no comic in the library +struct PlaceholderInList { + Placeholder placeholder; + ListEntry entry; +}; + +// an issue that is expected in a folder and has no comic there +struct PlaceholderInFolder { + Placeholder placeholder; + FolderId folderId; +}; + +} + +using ComicItem = std::variant; + +// the descriptive metadata, which every case has +const DescriptiveComicMetadata &rowMetadata(const ComicItem &item); +// the comic of a row with a file +const ComicDB *rowComic(const ComicItem &item); +// the file name of the row, or the last known one of a deleted comic; a placeholder has none +QString rowFileName(const ComicItem &item); + +// shared by the desktop views and the server, so both show the same rows in the same order +namespace ComicItems { + +QList fromQuery(QSqlQuery &query); +// the sorting of lists is done in the sql query +QList fromQuery(QSqlQuery &query, ListKind listKind); +// the comics and placeholders of a folder, in the folder order +QList loadFolder(FolderId folderId, QSqlDatabase &db); +// the entries of a label, in their order +QList loadLabel(LabelId labelId, QSqlDatabase &db); +// the entries of a reading list, then the entries of each of its child lists, in their order +QList loadReadingList(ReadingListId readingListId, QSqlDatabase &db, bool *hasChildLists = nullptr); +// the comics that match the query of a smart list, in the search order; nothing when the list does not exist, no rows +// when its query cannot be parsed +std::optional> loadSmartList(SmartListId smartListId, QSqlDatabase &db); +// the comics with a file added in the last `days` days, the newest first +QList loadRecent(qint64 days, QSqlDatabase &db); + +bool folderLessThan(const ComicItem &c1, const ComicItem &c2); +// search results and smart lists: each series stays together, in volume and number order +bool searchLessThan(const ComicItem &c1, const ComicItem &c2); +QList sortedForFolder(QList items); +// by series, volume, number, file name, and path, in natural order +QList sortedForSearch(QList items); + +} + +// what can be done with a row; ComicModel::supports answers it for each case +enum class ComicAction { + Open, + OpenContainingFolder, + ChangeFile, // rename, organize, delete from disk, cover from the file + SaveCover, + SetReadState, // read, unread, rating + SetType, + EditMetadata, + DeleteMetadata, + SearchComicVine, + AssignNumbers, + SetCustomCover, + AddToFavorites, + AddToList, + RemoveFromList, + RemoveFromFolder, // a missing comic of a folder + MatchToLibraryComic, // a missing entry or a deleted comic gets a comic with a file }; -//! [0] #endif diff --git a/YACReaderLibrary/db/comic_model.cpp b/YACReaderLibrary/db/comic_model.cpp index bcaab8543..4d8beceeb 100644 --- a/YACReaderLibrary/db/comic_model.cpp +++ b/YACReaderLibrary/db/comic_model.cpp @@ -6,15 +6,20 @@ #include "comic_item.h" #include "data_base_management.h" #include "db_helper.h" +#include "overloaded.h" #include "qnaturalsorting.h" #include "reading_list_model.h" #include "yacreader_global_gui.h" +#include +#include #include #include #include #include +#include +#include #include #ifdef use_unarr @@ -24,20 +29,361 @@ // ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read #include "QsLog.h" -auto defaultFolderContentSortFunction = [](const ComicItem *c1, const ComicItem *c2) { - return comicNumberLessThan(c1->data(ComicModel::Number), c1->data(ComicModel::FileName).toString(), - c2->data(ComicModel::Number), c2->data(ComicModel::FileName).toString()); -}; +namespace { + +// nullptr for a placeholder, which has no comic_info +const ComicInfo *rowInfo(const ComicItem &item) +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &row) -> const ComicInfo * { return &row.comic.info; }, + [](const ComicItemCase::ComicInList &row) -> const ComicInfo * { return &row.comic.info; }, + [](const ComicItemCase::DeletedComicInList &row) -> const ComicInfo * { return &row.info; }, + [](const ComicItemCase::PlaceholderInList &) -> const ComicInfo * { return nullptr; }, + [](const ComicItemCase::PlaceholderInFolder &) -> const ComicInfo * { return nullptr; }, + }, + item); +} + +ComicInfo *rowInfo(ComicItem &item) +{ + return const_cast(rowInfo(std::as_const(item))); +} + +const Placeholder *rowPlaceholder(const ComicItem &item) +{ + if (const auto *row = std::get_if(&item)) + return &row->placeholder; + if (const auto *row = std::get_if(&item)) + return &row->placeholder; + return nullptr; +} + +Placeholder *rowPlaceholder(ComicItem &item) +{ + return const_cast(rowPlaceholder(std::as_const(item))); +} + +const ListEntry *rowEntry(const ComicItem &item) +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &) -> const ListEntry * { return nullptr; }, + [](const ComicItemCase::ComicInList &row) -> const ListEntry * { return &row.entry; }, + [](const ComicItemCase::DeletedComicInList &row) -> const ListEntry * { return &row.entry; }, + [](const ComicItemCase::PlaceholderInList &row) -> const ListEntry * { return &row.entry; }, + [](const ComicItemCase::PlaceholderInFolder &) -> const ListEntry * { return nullptr; }, + }, + item); +} + +ComicRowId rowIdOf(const ComicItem &item) +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &row) -> ComicRowId { return row.comic.file.id; }, + [](const ComicItemCase::ComicInList &row) -> ComicRowId { return row.entry.id; }, + [](const ComicItemCase::DeletedComicInList &row) -> ComicRowId { return row.entry.id; }, + [](const ComicItemCase::PlaceholderInList &row) -> ComicRowId { return row.entry.id; }, + [](const ComicItemCase::PlaceholderInFolder &row) -> ComicRowId { return row.placeholder.id; }, + }, + item); +} + +bool supportsAction(const ComicItem &item, ComicAction action) +{ + const auto withFile = [action] { + switch (action) { + case ComicAction::Open: + case ComicAction::OpenContainingFolder: + case ComicAction::ChangeFile: + case ComicAction::SaveCover: + case ComicAction::SetReadState: + case ComicAction::SetType: + case ComicAction::EditMetadata: + case ComicAction::DeleteMetadata: + case ComicAction::SearchComicVine: + case ComicAction::AssignNumbers: + case ComicAction::SetCustomCover: + case ComicAction::AddToFavorites: + case ComicAction::AddToList: + return true; + case ComicAction::RemoveFromList: + case ComicAction::RemoveFromFolder: + case ComicAction::MatchToLibraryComic: + return false; + } + return false; + }; + + return std::visit(overloaded { + [&](const ComicItemCase::Comic &) { return withFile(); }, + [&](const ComicItemCase::ComicInList &) { return action == ComicAction::RemoveFromList || withFile(); }, + [action](const ComicItemCase::DeletedComicInList &) { + switch (action) { + case ComicAction::SaveCover: + case ComicAction::SetReadState: + case ComicAction::SetType: + case ComicAction::EditMetadata: + case ComicAction::DeleteMetadata: + case ComicAction::AssignNumbers: + case ComicAction::SearchComicVine: + case ComicAction::SetCustomCover: + case ComicAction::AddToList: + case ComicAction::RemoveFromList: + case ComicAction::MatchToLibraryComic: + return true; + // Favorites is keyed by comic id + case ComicAction::AddToFavorites: + case ComicAction::Open: + case ComicAction::OpenContainingFolder: + case ComicAction::ChangeFile: + case ComicAction::RemoveFromFolder: + return false; + } + return false; + }, + [action](const ComicItemCase::PlaceholderInList &row) { + switch (action) { + case ComicAction::SaveCover: + return row.placeholder.coverKey.has_value(); + case ComicAction::EditMetadata: + case ComicAction::SearchComicVine: + case ComicAction::SetCustomCover: + case ComicAction::AssignNumbers: + case ComicAction::AddToList: + case ComicAction::RemoveFromList: + case ComicAction::MatchToLibraryComic: + return true; + // no file, reading state, type, or rating + case ComicAction::Open: + case ComicAction::OpenContainingFolder: + case ComicAction::ChangeFile: + case ComicAction::SetReadState: + case ComicAction::SetType: + case ComicAction::DeleteMetadata: + case ComicAction::AddToFavorites: + case ComicAction::RemoveFromFolder: + return false; + } + return false; + }, + [action](const ComicItemCase::PlaceholderInFolder &row) { + switch (action) { + case ComicAction::SaveCover: + return row.placeholder.coverKey.has_value(); + case ComicAction::EditMetadata: + case ComicAction::SearchComicVine: + case ComicAction::SetCustomCover: + case ComicAction::AssignNumbers: + case ComicAction::AddToList: + case ComicAction::RemoveFromFolder: + case ComicAction::MatchToLibraryComic: + return true; + case ComicAction::Open: + case ComicAction::OpenContainingFolder: + case ComicAction::ChangeFile: + case ComicAction::SetReadState: + case ComicAction::SetType: + case ComicAction::DeleteMetadata: + case ComicAction::AddToFavorites: + case ComicAction::RemoveFromList: + return false; + } + return false; + }, + }, + item); +} + +bool sameRow(const ComicItem &a, const ComicItem &b) +{ + return rowIdOf(a) == rowIdOf(b); +} + +bool sameValues(const ComicItem &a, const ComicItem &b) +{ + if (a.index() != b.index() || !sameRow(a, b)) + return false; + + const auto *entryA = rowEntry(a); + const auto *entryB = rowEntry(b); + if (entryA != nullptr && *entryA != *entryB) + return false; + + const auto *comicA = rowComic(a); + const auto *comicB = rowComic(b); + if (comicA != nullptr && comicA->file != comicB->file) + return false; + + if (const auto *placeholderA = rowPlaceholder(a)) + return placeholderA->hasSameValues(*rowPlaceholder(b)); + return rowInfo(a)->hasSameValues(*rowInfo(b)); +} + +// what kind of row this is and where it is; a role that the case does not have is nothing to show +QVariant identityValue(const ComicItem &item, int role) +{ + return std::visit(overloaded { + [role](const ComicItemCase::Comic &) -> QVariant { + switch (role) { + case ComicModel::KindRole: + return QStringLiteral("Comic"); + case ComicModel::AvailableRole: + return true; + } + return { }; + }, + [role](const ComicItemCase::ComicInList &row) -> QVariant { + switch (role) { + case ComicModel::KindRole: + return QStringLiteral("ComicInList"); + case ComicModel::AvailableRole: + return true; + case ComicModel::ListItemIdRole: + return row.entry.id.id; + case ComicModel::ListIdRole: + return row.entry.listId; + } + return { }; + }, + [role](const ComicItemCase::DeletedComicInList &row) -> QVariant { + switch (role) { + case ComicModel::KindRole: + return QStringLiteral("DeletedComicInList"); + case ComicModel::AvailableRole: + return false; + case ComicModel::LastKnownNameRole: + return row.entry.lastKnownFile ? QVariant(row.entry.lastKnownFile->name) : QVariant(); + case ComicModel::ListItemIdRole: + return row.entry.id.id; + case ComicModel::ListIdRole: + return row.entry.listId; + } + return { }; + }, + [role](const ComicItemCase::PlaceholderInList &row) -> QVariant { + switch (role) { + case ComicModel::KindRole: + return QStringLiteral("PlaceholderInList"); + case ComicModel::AvailableRole: + return false; + case ComicModel::ListItemIdRole: + return row.entry.id.id; + case ComicModel::ListIdRole: + return row.entry.listId; + } + return { }; + }, + [role](const ComicItemCase::PlaceholderInFolder &) -> QVariant { + switch (role) { + case ComicModel::KindRole: + return QStringLiteral("PlaceholderInFolder"); + case ComicModel::AvailableRole: + return false; + } + return { }; + }, + }, + item); +} + +// the file columns; a deleted comic shows its last known file and has no id or folder +QVariant fileColumnValue(const ComicItem &item, int column) +{ + const auto fromFile = [column](const ComicFile &file) -> QVariant { + switch (column) { + case ComicModel::FileName: + return file.fileName; + case ComicModel::Id: + return file.id.value(); + case ComicModel::Parent_Id: + return file.folderId.value(); + case ComicModel::Path: + return file.path; + } + return { }; + }; + + return std::visit(overloaded { + [&](const ComicItemCase::Comic &row) { return fromFile(row.comic.file); }, + [&](const ComicItemCase::ComicInList &row) { return fromFile(row.comic.file); }, + [column](const ComicItemCase::DeletedComicInList &row) -> QVariant { + if (!row.entry.lastKnownFile) + return { }; + switch (column) { + case ComicModel::FileName: + return row.entry.lastKnownFile->name; + case ComicModel::Path: + return row.entry.lastKnownFile->path; + } + return { }; + }, + [](const ComicItemCase::PlaceholderInList &) -> QVariant { return { }; }, + [](const ComicItemCase::PlaceholderInFolder &) -> QVariant { return { }; }, + }, + item); +} + +// a placeholder has the descriptive columns only +QVariant columnValue(const ComicItem &item, int column) +{ + const auto &metadata = rowMetadata(item); + switch (column) { + case ComicModel::Number: + return metadata.number; + case ComicModel::Title: + return metadata.title; + case ComicModel::PublicationDate: + return metadata.date; + case ComicModel::Series: + return metadata.series; + case ComicModel::Volume: + return metadata.volume; + case ComicModel::StoryArc: + return metadata.storyArc; + case ComicModel::FileName: + case ComicModel::Id: + case ComicModel::Parent_Id: + case ComicModel::Path: + return fileColumnValue(item, column); + } + + const auto *infoOfRow = rowInfo(item); + if (infoOfRow == nullptr) + return { }; + const auto &info = *infoOfRow; + switch (column) { + case ComicModel::NumPages: + return info.numPages; + case ComicModel::Hash: + return info.hash; + case ComicModel::ReadColumn: + return info.read; + case ComicModel::CurrentPage: + return info.currentPage; + case ComicModel::Rating: + return info.rating; + case ComicModel::HasBeenOpened: + return qlonglong(info.hasBeenOpened ? 1 : 0); // the views got the SQL integer + case ComicModel::Added: + return info.added; + case ComicModel::Type: + return info.type; + case ComicModel::LastTimeOpened: + return info.lastTimeOpened; + } + return QVariant(); +} + +} ComicModel::ComicModel(QObject *parent) : QAbstractItemModel(parent), showRecent(false), recentDays(1) { + connect(this, &QAbstractItemModel::modelAboutToBeReset, this, [this] { coverFileExistence.clear(); }); } ComicModel::~ComicModel() { - qDeleteAll(_data); } int ComicModel::columnCount(const QModelIndex &parent) const @@ -45,7 +391,7 @@ int ComicModel::columnCount(const QModelIndex &parent) const Q_UNUSED(parent) if (_data.isEmpty()) return 0; - return _data.first()->columnCount() + 1 /* + the number of calculated columns */; + return ComicModel::State + 1; } bool ComicModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const @@ -68,84 +414,47 @@ bool ComicModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int if (!canDropMimeData(data, action, row, column, parent)) return false; - const QList comicIds = YACReader::mimeDataToComicsIds(data); - if (comicIds.isEmpty()) + const auto rowIds = YACReader::mimeDataToRows(data); + if (rowIds.isEmpty()) return false; if (row < 0 || row > _data.count()) row = _data.count(); QList currentIndexes; - int i; - { - const auto ¤tData = _data; - for (const auto id : comicIds) { - i = 0; - for (auto *item : currentData) { - if (item->data(Id) == id) { - currentIndexes << i; - break; - } - i++; - } - } + for (const auto ¤t : indexesOf(rowIds)) { + if (!current.isValid()) + return false; + currentIndexes << current.row(); } - if (currentIndexes.size() != comicIds.size()) - return false; - std::sort(currentIndexes.begin(), currentIndexes.end()); - QList resortedData; + const QSet draggedRows(currentIndexes.cbegin(), currentIndexes.cend()); - if (currentIndexes.contains(row)) // no resorting + if (draggedRows.contains(row)) // no resorting return false; - ComicItem *destinationItem; - if (row == -1 || row >= _data.length()) - destinationItem = 0; - else - destinationItem = _data.at(row); + const int destinationRow = (row == -1 || row >= _data.length()) ? -1 : row; QList newSorting; - - const auto ¤tIndexList = currentIndexes; - { - const auto ¤tData = _data; - i = 0; - for (auto *item : currentData) { - if (!currentIndexes.contains(i)) { - - if (item == destinationItem) { - for (const auto index : currentIndexList) { - resortedData << _data.at(index); - newSorting << index; - } - } - - resortedData << item; - newSorting << i; - } - - i++; - } - } - - if (destinationItem == 0) { - for (const auto index : currentIndexList) { - resortedData << _data.at(index); - newSorting << index; - } + newSorting.reserve(_data.size()); + for (int i = 0; i < _data.length(); i++) { + if (draggedRows.contains(i)) + continue; + if (i == destinationRow) + newSorting << currentIndexes; + newSorting << i; } + if (destinationRow == -1) + newSorting << currentIndexes; QLOG_TRACE() << newSorting; int tempRow = row; - for (const auto id : comicIds) { - int i = 0; - const auto dataSnapshot = _data; - for (auto *item : dataSnapshot) { - if (item->data(Id) == id) { + for (const auto &rowId : std::as_const(rowIds)) { + for (int i = 0; i < _data.size(); i++) { + if (rowIdOf(_data.at(i)) == rowId) { const bool skipElement = i == tempRow || i + 1 == tempRow; if (!skipElement) { @@ -165,16 +474,23 @@ bool ComicModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int break; } - i++; } } // TODO fix selection QList allComicIds; - const auto &allData = _data; - for (auto *item : allData) { - allComicIds << item->data(Id).toULongLong(); + QList allEntryIds; + for (const auto &item : std::as_const(_data)) { + std::visit(overloaded { + [&allComicIds](const ComicItemCase::Comic &row) { allComicIds << row.comic.file.id.value(); }, + [&allEntryIds](const ComicItemCase::ComicInList &row) { allEntryIds << row.entry.id; }, + [&allEntryIds](const ComicItemCase::DeletedComicInList &row) { allEntryIds << row.entry.id; }, + [&allEntryIds](const ComicItemCase::PlaceholderInList &row) { allEntryIds << row.entry.id; }, + [](const ComicItemCase::PlaceholderInFolder &) { }, + }, + item); } + bool reordered = true; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); @@ -183,21 +499,28 @@ bool ComicModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int DBHelper::reasignOrderToComicsInFavorites(allComicIds, db); break; case Label: - DBHelper::reasignOrderToComicsInLabel(sourceId, allComicIds, db); + reordered = DBHelper::reorderLabelEntries(LabelId(sourceId), allEntryIds, db); break; case ReadingList: - DBHelper::reasignOrderToComicsInReadingList(sourceId, allComicIds, db); + reordered = DBHelper::reorderReadingListEntries(ReadingListId(sourceId), allEntryIds, db); break; case Folder: case Reading: case Recent: case SearchResult: + case SmartList: break; } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + // the rows show the stored order again + if (!reordered) { + reload(); + return false; + } + // endMoveRows(); emit resortedIndexes(newSorting); @@ -216,14 +539,16 @@ bool ComicModel::canBeResorted() return enableResorting; } +// comic ids for Favorites, file moves, and the drop checks, and the rows in order QMimeData *ComicModel::mimeData(const QModelIndexList &indexes) const { - // custom model data - // application/yacreader-comics-ids + list of ids in a QByteArray QList ids; + QList rows; for (const auto &index : indexes) { - QLOG_DEBUG() << "dragging : " << index.data(IdRole).toULongLong(); - ids << index.data(IdRole).toULongLong(); + const auto &item = _data.at(index.row()); + if (const auto *comic = rowComic(item)) + ids << comic->file.id.value(); + rows << rowIdOf(item); } QByteArray data; @@ -232,6 +557,7 @@ QMimeData *ComicModel::mimeData(const QModelIndexList &indexes) const auto mimeData = new QMimeData(); mimeData->setData(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat, data); + YACReader::setRowsMimeData(mimeData, rows); return mimeData; } @@ -271,6 +597,11 @@ QHash ComicModel::roleNames() const roles[SeriesRole] = "series"; roles[VolumeRole] = "volume"; roles[StoryArcRole] = "story_arc"; + roles[KindRole] = "kind"; + roles[AvailableRole] = "available"; + roles[LastKnownNameRole] = "last_known_name"; + roles[ListItemIdRole] = "list_item_id"; + roles[ListIdRole] = "list_id"; return roles; } @@ -303,10 +634,15 @@ QVariant ComicModel::data(const QModelIndex &index, int role) const // TODO check here if any view is asking for TableModel::Roles // these roles will be used from QML/GridView - auto item = static_cast(index.internalPointer()); + const auto &item = _data.at(index.row()); + const auto *placeholder = rowPlaceholder(item); + const auto value = [&item](int column) { return columnValue(item, column); }; + // Display fallback only; the metadata and API keep their original values. + const auto orDash = [](const QString &title) { return title.isEmpty() ? QStringLiteral("-") : title; }; + const auto title = [&] { return orDash(titleOrFileName(rowMetadata(item), rowFileName(item))); }; auto sizeString = [=] { - auto bytes = item->data(ComicModel::Hash).toString().right(item->data(ComicModel::Hash).toString().length() - 40).toULongLong(); + auto bytes = value(ComicModel::Hash).toString().right(value(ComicModel::Hash).toString().length() - 40).toULongLong(); QStringList units = { "B", "KB", "MB", "GB", "TB" }; int i; @@ -321,83 +657,103 @@ QVariant ComicModel::data(const QModelIndex &index, int role) const }; if (role == NumberRole) - return item->data(Number); + return value(Number); else if (role == TitleRole) - return item->data(Title).isNull() ? item->data(FileName) : item->data(Title); + return title(); else if (role == ReadableTitle) { - QString title; - if (!item->data(Number).isNull()) { - title = title % "#" % item->data(Number).toString() % " "; - } - return QVariant(title % (item->data(Title).isNull() ? item->data(FileName).toString() : item->data(Title).toString())); + const auto &metadata = rowMetadata(item); + // Continue-reading cards and action labels have no separate number field. + return orDash(titleIncludingNumber(metadata, rowFileName(item))); } else if (role == FileNameRole) - return item->data(FileName); + return rowComic(item) ? value(FileName) : QVariant(); else if (role == RatingRole) - return item->data(Rating); + return value(Rating); else if (role == CoverPathRole) { - auto coverUrl = getCoverUrlPathForComicHash(item->data(Hash).toString()); - const auto revision = coverRevisions.value(item->data(Id).toULongLong()); + if (placeholder != nullptr) { + const auto coverPath = placeholder->coverKey ? LibraryPaths::placeholderCoverPathFromDataPath(_databasePath, *placeholder->coverKey) : QString(); + return coverPath.isEmpty() || !coverFileExists(coverPath) ? QUrl(QStringLiteral("qrc:/images/unavailableCover.png")) : QUrl::fromLocalFile(coverPath); + } + auto coverUrl = getCoverUrlPathForComicHash(value(Hash).toString()); + if (!rowComic(item) && !coverFileExists(coverUrl.toLocalFile())) + return QUrl(QStringLiteral("qrc:/images/unavailableCover.png")); + const auto revision = coverRevisions.value(rowInfo(item)->id.value()); if (revision > 0) coverUrl.setQuery(QStringLiteral("revision=%1").arg(revision)); return coverUrl; } else if (role == NumPagesRole) - return item->data(NumPages); + return value(NumPages); else if (role == CurrentPageRole) - return item->data(CurrentPage); + return value(CurrentPage); else if (role == ReadColumnRole) - return item->data(ReadColumn).toBool(); + return value(ReadColumn).toBool(); else if (role == HasBeenOpenedRole) - return item->data(HasBeenOpened); + return value(HasBeenOpened); else if (role == IdRole) - return item->data(Id); + return value(Id); else if (role == HashRole) - return item->data(Hash); + return value(Hash); else if (role == PublicationDateRole) - return QVariant(localizedDate(item->data(PublicationDate).toString())); + return QVariant(localizedDate(value(PublicationDate).toString())); else if (role == AddedRole) - return item->data(Added); + return value(Added); else if (role == TypeRole) - return item->data(Type); + return value(Type); else if (role == ShowRecentRole) return showRecent; else if (role == RecentRangeRole) return recentDays * 86400; else if (role == SizeRole) - return sizeString(); + return placeholder != nullptr ? QVariant() : QVariant(sizeString()); else if (role == SeriesRole) - return item->data(Series); + return value(Series); else if (role == VolumeRole) - return item->data(Volume); + return value(Volume); else if (role == StoryArcRole) - return item->data(StoryArc); + return value(StoryArc); + else if (role == KindRole || role == AvailableRole || role == LastKnownNameRole || role == ListItemIdRole || role == ListIdRole) + return identityValue(item, role); if (role != Qt::DisplayRole) return QVariant(); if (index.column() == ComicModel::Hash) - return item->data(ComicModel::Hash).toString(); + return value(ComicModel::Hash); + if (index.column() == ComicModel::Title) + return value(ComicModel::Title); + // a placeholder shows "-" for the values it does not have + if (placeholder != nullptr) { + switch (index.column()) { + case ComicModel::FileName: + case ComicModel::NumPages: + case ComicModel::CurrentPage: + case ComicModel::Size: + return QStringLiteral("-"); + case ComicModel::ReadColumn: + return QVariant(); + } + } if (index.column() == ComicModel::Size) return sizeString(); if (index.column() == ComicModel::ReadColumn) - return (item->data(ComicModel::CurrentPage).toInt() == item->data(ComicModel::NumPages).toInt() || item->data(ComicModel::ReadColumn).toBool()) ? QVariant(tr("yes")) : QVariant(tr("no")); + return (value(ComicModel::CurrentPage).toInt() == value(ComicModel::NumPages).toInt() || value(ComicModel::ReadColumn).toBool()) ? QVariant(tr("yes")) : QVariant(tr("no")); if (index.column() == ComicModel::CurrentPage) - return item->data(ComicModel::HasBeenOpened).toBool() ? item->data(index.column()) : QVariant("-"); + return value(ComicModel::HasBeenOpened).toBool() ? value(index.column()) : QVariant("-"); if (index.column() == ComicModel::Rating) return QVariant(); if (index.column() == ComicModel::PublicationDate) { - return QVariant(localizedDate(item->data(PublicationDate).toString())); + return QVariant(localizedDate(value(PublicationDate).toString())); } - return item->data(index.column()); + return value(index.column()); } Qt::ItemFlags ComicModel::flags(const QModelIndex &index) const { if (!index.isValid()) return { }; - if (index.column() == ComicModel::Rating) + if (index.column() == ComicModel::Rating && supports(index, ComicAction::SetReadState)) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; } @@ -450,7 +806,7 @@ QVariant ComicModel::headerData(int section, Qt::Orientation orientation, } if (orientation == Qt::Vertical && role == Qt::DecorationRole) { - QString fileName = _data.value(section)->data(ComicModel::FileName).toString(); + const QString fileName = (section >= 0 && section < _data.size()) ? rowFileName(_data.at(section)) : QString(); QFileInfo fi(fileName); QString ext = fi.suffix(); @@ -487,7 +843,7 @@ QModelIndex ComicModel::index(int row, int column, const QModelIndex &parent) if (!hasIndex(row, column, parent)) return QModelIndex(); - return createIndex(row, column, _data.at(row)); + return createIndex(row, column); } QModelIndex ComicModel::parent(const QModelIndex &index) const @@ -510,32 +866,37 @@ int ComicModel::rowCount(const QModelIndex &parent) const QStringList ComicModel::getPaths(const QString &_source) { QStringList paths; - QList::ConstIterator itr; - for (itr = _data.constBegin(); itr != _data.constEnd(); itr++) { - QString hash = (*itr)->data(ComicModel::Hash).toString(); - paths << LibraryPaths::coverPath(_source, hash); + for (const auto &item : std::as_const(_data)) { + QString path; + if (const auto *placeholder = rowPlaceholder(item)) + path = placeholder->coverKey ? LibraryPaths::placeholderCoverPathFromDataPath(LibraryPaths::libraryDataPath(_source), *placeholder->coverKey) : QString(); + else + path = LibraryPaths::coverPath(_source, rowInfo(item)->hash); + if (rowComic(item) == nullptr && (path.isEmpty() || !coverFileExists(path))) + path = QStringLiteral(":/images/unavailableCover.png"); + paths << path; } return paths; } -#define COMIC_MODEL_QUERY_FIELDS "ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date,ci.added,ci.type,ci.lastTimeOpened,ci.series,ci.volume,ci.storyArc" +QList ComicModel::getUnavailableList() const +{ + QList unavailable; + unavailable.reserve(_data.size()); + for (const auto &item : std::as_const(_data)) + unavailable.append(rowComic(item) == nullptr); + return unavailable; +} -QList ComicModel::createFolderModelData(unsigned long long folderId, const QString &databasePath) const +QList ComicModel::createFolderModelData(unsigned long long folderId, const QString &databasePath) const { - QList modelData; + QList modelData; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "WHERE c.parentId = :parentId"); - selectQuery.bindValue(":parentId", folderId); - selectQuery.exec(); - - modelData = createModelData(selectQuery); + modelData = ComicItems::loadFolder(FolderId(folderId), db); connectionName = db.connectionName(); } @@ -551,7 +912,6 @@ void ComicModel::setupFolderModelData(unsigned long long int folderId, const QSt sourceId = folderId; beginResetModel(); - qDeleteAll(_data); _data.clear(); _databasePath = databasePath; @@ -561,22 +921,14 @@ void ComicModel::setupFolderModelData(unsigned long long int folderId, const QSt endResetModel(); } -QList ComicModel::createLabelModelData(unsigned long long parentLabel, const QString &databasePath) const +QList ComicModel::createLabelModelData(unsigned long long parentLabel, const QString &databasePath) const { - QList modelData; + QList modelData; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "INNER JOIN comic_label cl ON (c.id == cl.comic_id) " - "WHERE cl.label_id = :parentLabelId " - "ORDER BY cl.ordering"); - selectQuery.bindValue(":parentLabelId", parentLabel); - selectQuery.exec(); - modelData = createModelDataForList(selectQuery); + modelData = ComicItems::loadLabel(LabelId(parentLabel), db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -591,7 +943,6 @@ void ComicModel::setupLabelModelData(unsigned long long parentLabel, const QStri sourceId = parentLabel; beginResetModel(); - qDeleteAll(_data); _data.clear(); _databasePath = databasePath; @@ -601,41 +952,16 @@ void ComicModel::setupLabelModelData(unsigned long long parentLabel, const QStri endResetModel(); } -QList ComicModel::createReadingListData(unsigned long long parentReadingList, const QString &databasePath, bool &enableResorting) const +QList ComicModel::createReadingListData(unsigned long long parentReadingList, const QString &databasePath, bool &enableResorting) const { - QList modelData; + QList modelData; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - QList ids; - ids << parentReadingList; - - QSqlQuery subfolders(db); - subfolders.prepare("SELECT id " - "FROM reading_list " - "WHERE parentId = :parentId " - "ORDER BY ordering ASC"); - subfolders.bindValue(":parentId", parentReadingList); - subfolders.exec(); - while (subfolders.next()) - ids << subfolders.record().value(0).toULongLong(); - - enableResorting = ids.length() == 1; // only resorting if no sublists exist - - const auto &readingListIds = ids; - for (const auto id : readingListIds) { - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "INNER JOIN comic_reading_list crl ON (c.id == crl.comic_id) " - "WHERE crl.reading_list_id = :parentReadingList " - "ORDER BY crl.ordering"); - selectQuery.bindValue(":parentReadingList", id); - selectQuery.exec(); - - modelData << createModelDataForList(selectQuery); - } + bool hasChildLists = false; + modelData = ComicItems::loadReadingList(ReadingListId(parentReadingList), db, &hasChildLists); + enableResorting = !hasChildLists; // only resorting if no sublists exist connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -649,7 +975,6 @@ void ComicModel::setupReadingListModelData(unsigned long long parentReadingList, sourceId = parentReadingList; beginResetModel(); - qDeleteAll(_data); _data.clear(); _databasePath = databasePath; @@ -659,22 +984,55 @@ void ComicModel::setupReadingListModelData(unsigned long long parentReadingList, endResetModel(); } -QList ComicModel::createFavoritesModelData(const QString &databasePath) const +// a smart list that no longer exists has no rows +QList ComicModel::createSmartListModelData(unsigned long long smartListId, const QString &databasePath) const +{ + QList modelData; + + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); + modelData = ComicItems::loadSmartList(SmartListId(smartListId), db).value_or(QList()); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + return modelData; +} + +void ComicModel::setupSmartListModelData(unsigned long long smartListId, const QString &databasePath) +{ + enableResorting = false; + mode = SmartList; + sourceId = smartListId; + + beginResetModel(); + _data.clear(); + + _databasePath = databasePath; + + takeData(createSmartListModelData(smartListId, databasePath)); + + endResetModel(); +} + +QList ComicModel::createFavoritesModelData(const QString &databasePath) const { - QList modelData; + QList modelData; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) " - "WHERE cdrl.default_reading_list_id = :parentDefaultListId " - "ORDER BY cdrl.ordering"); + selectQuery.setForwardOnly(true); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " " + "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " + "INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) " + "WHERE cdrl.default_reading_list_id = :parentDefaultListId " + "ORDER BY cdrl.ordering"); selectQuery.bindValue(":parentDefaultListId", 1); selectQuery.exec(); - modelData = createModelDataForList(selectQuery); + modelData = ComicItems::fromQuery(selectQuery); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -689,7 +1047,6 @@ void ComicModel::setupFavoritesModelData(const QString &databasePath) sourceId = -1; beginResetModel(); - qDeleteAll(_data); _data.clear(); _databasePath = databasePath; @@ -699,21 +1056,22 @@ void ComicModel::setupFavoritesModelData(const QString &databasePath) endResetModel(); } -QList ComicModel::createReadingModelData(const QString &databasePath) const +QList ComicModel::createReadingModelData(const QString &databasePath) const { - QList modelData; + QList modelData; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "WHERE ci.hasBeenOpened = 1 AND ci.read = 0 " - "ORDER BY ci.lastTimeOpened DESC"); + selectQuery.setForwardOnly(true); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " " + "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " + "WHERE ci.hasBeenOpened = 1 AND ci.read = 0 " + "ORDER BY ci.lastTimeOpened DESC"); selectQuery.exec(); - modelData = createModelDataForList(selectQuery); + modelData = ComicItems::fromQuery(selectQuery); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -728,7 +1086,6 @@ void ComicModel::setupReadingModelData(const QString &databasePath) sourceId = -1; beginResetModel(); - qDeleteAll(_data); _data.clear(); _databasePath = databasePath; @@ -738,22 +1095,14 @@ void ComicModel::setupReadingModelData(const QString &databasePath) endResetModel(); } -QList ComicModel::createRecentModelData(const QString &databasePath) const +QList ComicModel::createRecentModelData(const QString &databasePath) const { - QList modelData; + QList modelData; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT " COMIC_MODEL_QUERY_FIELDS " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "WHERE ci.added > :limit " - "ORDER BY ci.added DESC"); - selectQuery.bindValue(":limit", QDateTime::currentDateTime().addDays(-recentDays).toSecsSinceEpoch()); - selectQuery.exec(); - - modelData = createModelDataForList(selectQuery); + modelData = ComicItems::loadRecent(recentDays, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -768,7 +1117,6 @@ void ComicModel::setupRecentModelData(const QString &databasePath) sourceId = -1; beginResetModel(); - qDeleteAll(_data); _data.clear(); _databasePath = databasePath; @@ -778,7 +1126,7 @@ void ComicModel::setupRecentModelData(const QString &databasePath) endResetModel(); } -void ComicModel::setModelData(QList *data, const QString &databasePath) +void ComicModel::setModelData(QList *data, const QString &databasePath) { enableResorting = false; mode = SearchResult; @@ -788,87 +1136,43 @@ void ComicModel::setModelData(QList *data, const QString &databaseP beginResetModel(); - qDeleteAll(_data); - - _data.clear(); - - _data.append(*data); + _data = *data; endResetModel(); - emit searchNumResults(_data.length()); - delete data; } QString ComicModel::getComicPath(QModelIndex mi) { if (mi.isValid()) - return _data.at(mi.row())->data(ComicModel::Path).toString(); + if (const auto *comic = rowComic(_data.at(mi.row()))) + return comic->file.path; return ""; } -QList ComicModel::createModelData(QSqlQuery &sqlquery) const +void ComicModel::takeData(const QList &data) { - QList modelData; - - int numColumns = sqlquery.record().count(); - - while (sqlquery.next()) { - QList data; - - for (int i = 0; i < numColumns; i++) - data << sqlquery.value(i); - - modelData.append(new ComicItem(data)); - } - - std::sort(modelData.begin(), modelData.end(), defaultFolderContentSortFunction); - - return modelData; -} - -// the sorting is done in the sql query -QList ComicModel::createModelDataForList(QSqlQuery &sqlquery) const -{ - QList modelData; - - int numColumns = sqlquery.record().count(); - - while (sqlquery.next()) { - QList data; - for (int i = 0; i < numColumns; i++) - data << sqlquery.value(i); - - modelData.append(new ComicItem(data)); - } - - return modelData; -} - -void ComicModel::takeData(const QList &data) -{ - qDeleteAll(_data); _data = data; } -void ComicModel::takeUpdatedData(const QList &updatedData, std::function comparator) +void ComicModel::takeUpdatedData(const QList &updatedData, std::function comparator) { + coverFileExistence.clear(); int length = _data.size(); int lengthUpdated = updatedData.size(); int i; // index of the internal data int j; // index of the updated children for (i = 0, j = 0; i < length && j < lengthUpdated;) { - auto comic = _data.at(i); - auto updatedComic = updatedData.at(j); + const auto &comic = _data.at(i); + const auto &updatedComic = updatedData.at(j); - auto sameComic = comic->data(ComicModel::Id) == updatedComic->data(ComicModel::Id); - if (sameComic) { - if (comic->getData() != updatedComic->getData()) { + if (sameRow(comic, updatedComic)) { + if (!sameValues(comic, updatedComic)) { auto modelIndexToUpdate = index(i, 0, QModelIndex()); - comic->setData(updatedComic->getData()); + _data[i] = updatedComic; emit dataChanged(modelIndexToUpdate, modelIndexToUpdate); } @@ -922,36 +1226,24 @@ void ComicModel::takeUpdatedData(const QList &updatedData, std::fun for (int k = length - 1; k >= i; k--) { beginRemoveRows(QModelIndex(), k, k); - delete _data.at(k); _data.removeAt(k); endRemoveRows(); } } -ComicDB ComicModel::getComic(const QModelIndex &mi) +std::optional ComicModel::getComic(const QModelIndex &mi) { - ComicDB c; - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - bool found; - c = DBHelper::loadComic(_data.at(mi.row())->data(ComicModel::Id).toULongLong(), db, found); - connectionName = db.connectionName(); - } - QSqlDatabase::removeDatabase(connectionName); - - return c; -} + const auto *rowFile = rowComic(_data.at(mi.row())); + if (rowFile == nullptr) + return std::nullopt; -ComicDB ComicModel::_getComic(const QModelIndex &mi) -{ ComicDB c; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); bool found; - c = DBHelper::loadComic(_data.at(mi.row())->data(ComicModel::Id).toULongLong(), db, found); + c = DBHelper::loadComic(rowFile->file.id.value(), db, found); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -964,38 +1256,38 @@ QVector ComicModel::getReadList() int numComics = _data.count(); QVector readList(numComics); for (int i = 0; i < numComics; i++) { - if (_data.value(i)->data(ComicModel::ReadColumn).toBool()) + const auto *infoOfRow = rowInfo(_data.at(i)); + if (infoOfRow == nullptr) { + readList[i] = YACReader::Unread; + continue; + } + const auto &info = *infoOfRow; + if (info.read) readList[i] = YACReader::Read; - else if (_data.value(i)->data(ComicModel::CurrentPage).toInt() == _data.value(i)->data(ComicModel::NumPages).toInt()) + else if (info.currentPage == info.numPages.toInt()) readList[i] = YACReader::Read; - else if (_data.value(i)->data(ComicModel::HasBeenOpened).toBool()) + else if (info.hasBeenOpened) readList[i] = YACReader::Opened; else readList[i] = YACReader::Unread; } return readList; } -// TODO untested, this method is no longer used -QVector ComicModel::setAllComicsRead(YACReaderComicReadStatus read) -{ - return setComicsRead(persistentIndexList(), read); -} - -QList ComicModel::getAllComics() +QList ComicModel::getComics(QList list) { QList comics; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - db.transaction(); - int numComics = _data.count(); - for (int i = 0; i < numComics; i++) { - bool found; - comics.append(DBHelper::loadComic(_data.value(i)->data(ComicModel::Id).toULongLong(), db, found)); + QList ids; + ids.reserve(list.size()); + for (const auto &mi : std::as_const(list)) { + if (const auto *comic = rowComic(_data.at(mi.row()))) + ids.append(comic->file.id.value()); } + comics = DBHelper::loadComics(ids, db); - db.commit(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -1003,49 +1295,130 @@ QList ComicModel::getAllComics() return comics; } -QList ComicModel::getComics(QList list) +std::optional ComicModel::firstUnreadComic() const { - QList comics; - for (auto itr = list.constBegin(); itr != list.constEnd(); itr++) { - comics.append(_getComic(*itr)); + for (const auto &item : _data) { + const auto *comic = rowComic(item); + if (comic != nullptr && !comic->info.read) { + return *comic; + } } - return comics; + return std::nullopt; +} + +ComicRecord ComicModel::record(const QModelIndex &index) const +{ + return std::visit(overloaded { + [](const ComicItemCase::Comic &row) -> ComicRecord { return ComicRecordCase::Comic { row.comic }; }, + [](const ComicItemCase::ComicInList &row) -> ComicRecord { return ComicRecordCase::Comic { row.comic }; }, + [](const ComicItemCase::DeletedComicInList &row) -> ComicRecord { return ComicRecordCase::DeletedComic { row.info, row.entry.lastKnownFile }; }, + [](const ComicItemCase::PlaceholderInList &row) -> ComicRecord { return ComicRecordCase::Placeholder { row.placeholder }; }, + [](const ComicItemCase::PlaceholderInFolder &row) -> ComicRecord { return ComicRecordCase::Placeholder { row.placeholder }; }, + }, + _data.at(index.row())); +} + +std::optional ComicModel::listEntry(const QModelIndex &index) const +{ + const auto *entry = index.isValid() ? rowEntry(_data.at(index.row())) : nullptr; + return entry != nullptr ? std::optional(*entry) : std::nullopt; +} + +bool ComicModel::hasUnavailableRows() const +{ + return std::any_of(_data.cbegin(), _data.cend(), [](const ComicItem &item) { return rowComic(item) == nullptr; }); +} + +bool ComicModel::supports(const QModelIndex &index, ComicAction action) const +{ + if (!index.isValid()) + return false; + // Favorites and Reading remove by comic; Recent is read only + if (action == ComicAction::RemoveFromList && (mode == Favorites || mode == Reading)) + return true; + return supportsAction(_data.at(index.row()), action); +} + +QModelIndexList ComicModel::supportedIndexes(const QModelIndexList &indexes, ComicAction action) const +{ + QModelIndexList supported; + for (const auto &index : indexes) { + if (supports(index, action)) + supported.append(index); + } + return supported; +} + +void ComicModel::deleteMetadata(const QModelIndexList &indexes) +{ + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + db.transaction(); + QSet done; + for (const auto &mi : indexes) { + const auto *info = rowInfo(_data.at(mi.row())); + if (info == nullptr) + continue; + const auto id = info->id; + if (done.contains(id)) + continue; + done.insert(id); + if (auto info = DBHelper::loadComicInfo(id, db)) { + info->deleteMetadata(); + DBHelper::update(&*info, db); + } + } + db.commit(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + reload(); } // TODO QVector ComicModel::setComicsRead(QList list, YACReaderComicReadStatus read) { QString connectionName = ""; const auto &comicIndexes = list; + QSet changed; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); db.transaction(); for (const auto &mi : comicIndexes) { + const auto *rowComicInfo = rowInfo(_data.at(mi.row())); + if (rowComicInfo == nullptr) + continue; + const auto id = rowComicInfo->id; if (read == YACReader::Read) { - _data.value(mi.row())->setData(ComicModel::ReadColumn, QVariant(true)); - bool found; - ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db, found); - c.info.read = true; - DBHelper::update(&(c.info), db); + if (auto info = DBHelper::loadComicInfo(id, db)) { + info->read = true; + DBHelper::update(&*info, db); + } } if (read == YACReader::Unread) { - _data.value(mi.row())->setData(ComicModel::ReadColumn, QVariant(false)); - _data.value(mi.row())->setData(ComicModel::CurrentPage, QVariant(1)); - _data.value(mi.row())->setData(ComicModel::HasBeenOpened, QVariant(false)); - bool found; - ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db, found); - c.info.read = false; - c.info.currentPage = 1; - c.info.hasBeenOpened = false; - c.info.lastTimeOpened.setValue(QVariant()); - DBHelper::update(&(c.info), db); + if (auto info = DBHelper::loadComicInfo(id, db)) { + info->read = false; + info->currentPage = 1; + info->hasBeenOpened = false; + info->lastTimeOpened.setValue(QVariant()); + DBHelper::update(&*info, db); + } } + if (read == YACReader::Read || read == YACReader::Unread) + changed.insert(id); } db.commit(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); - emit dataChanged(index(list.first().row(), ComicModel::ReadColumn), index(list.last().row(), ComicModel::HasBeenOpened), QVector() << ReadColumnRole << CurrentPageRole << HasBeenOpenedRole); + updateRowsOf(changed, [read](ComicInfo &info) { + info.read = read == YACReader::Read; + if (read == YACReader::Unread) { + info.currentPage = 1; + info.hasBeenOpened = false; + } }, ComicModel::ReadColumn, ComicModel::HasBeenOpened, { ReadColumnRole, CurrentPageRole, HasBeenOpenedRole }); return getReadList(); } @@ -1054,44 +1427,84 @@ void ComicModel::setComicsType(QList list, FileType type) { QString connectionName = ""; const auto &comicIndexes = list; + QSet changed; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); db.transaction(); for (const auto &mi : comicIndexes) { - bool found; - ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db, found); - c.info.type = QVariant::fromValue(type); - DBHelper::update(&(c.info), db); + const auto *rowComicInfo = rowInfo(_data.at(mi.row())); + if (rowComicInfo == nullptr) + continue; + const auto id = rowComicInfo->id; + if (auto info = DBHelper::loadComicInfo(id, db)) { + info->type = QVariant::fromValue(type); + DBHelper::update(&*info, db); + } + changed.insert(id); } db.commit(); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); - for (const auto &mi : comicIndexes) { - _data.value(mi.row())->setData(ComicModel::Type, QVariant::fromValue(type)); + updateRowsOf(changed, [type](ComicInfo &info) { info.type = QVariant::fromValue(type); }, ComicModel::Type, ComicModel::Type, { TypeRole }); +} + +void ComicModel::updateRowsOf(const QSet &ids, const std::function &change, int firstColumn, int lastColumn, const QList &roles) +{ + if (ids.isEmpty()) + return; + QList rows; + for (int row = 0; row < _data.size(); row++) { + auto *info = rowInfo(_data[row]); + if (info == nullptr || !ids.contains(info->id)) + continue; + change(*info); + rows.append(row); } + emitRowRuns(rows, firstColumn, lastColumn, roles); +} - emit dataChanged(index(list.first().row(), ComicModel::Type), index(list.last().row(), ComicModel::Type), QVector() << TypeRole); +void ComicModel::emitRowRuns(const QList &rows, int firstColumn, int lastColumn, const QList &roles) +{ + for (qsizetype first = 0; first < rows.size();) { + auto last = first; + while (last + 1 < rows.size() && rows[last + 1] == rows[last] + 1) + last++; + emit dataChanged(index(rows[first], firstColumn), index(rows[last], lastColumn), roles); + first = last + 1; + } } -qint64 ComicModel::asignNumbers(QList list, int startingNumber) +// each comic_info or placeholder gets one number, at its first appearance in `list` +ComicRowId ComicModel::asignNumbers(QList list, int startingNumber) { - qint64 idFirst; + ComicRowId idFirst; QString connectionName = ""; - const auto &comicIndexes = list; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); db.transaction(); - idFirst = _data.value(list[0].row())->data(ComicModel::Id).toULongLong(); - int i = 0; - for (const auto &mi : comicIndexes) { - bool found; - ComicDB c = DBHelper::loadComic(_data.value(mi.row())->data(ComicModel::Id).toULongLong(), db, found); - c.info.number = startingNumber + i; - c.info.edited = true; - DBHelper::update(&(c.info), db); - i++; + idFirst = rowIdOf(_data.at(list[0].row())); + QSet numberedComics; + QSet numberedPlaceholders; + auto number = startingNumber; + for (const auto &mi : std::as_const(list)) { + const auto &item = _data.at(mi.row()); + DescriptiveMetadataPatch patch; + patch.set(&DescriptiveComicMetadata::number, QString::number(number)); + if (const auto *placeholder = rowPlaceholder(item)) { + if (numberedPlaceholders.contains(placeholder->id)) + continue; + numberedPlaceholders.insert(placeholder->id); + DBHelper::updatePlaceholderMetadata(placeholder->id, patch, db); + } else { + const auto id = rowInfo(item)->id; + if (numberedComics.contains(id)) + continue; + numberedComics.insert(id); + DBHelper::updateComicInfoMetadata(id, patch, db); + } + number++; } db.commit(); connectionName = db.connectionName(); @@ -1103,10 +1516,10 @@ qint64 ComicModel::asignNumbers(QList list, int startingNumber) } QModelIndex ComicModel::getIndexFromId(quint64 id) { - QList::ConstIterator itr; int i = 0; - for (itr = _data.constBegin(); itr != _data.constEnd(); itr++) { - if ((*itr)->data(ComicModel::Id).toULongLong() == id) + for (const auto &item : std::as_const(_data)) { + const auto *comic = rowComic(item); + if (comic != nullptr && comic->file.id.value() == id) break; i++; } @@ -1114,6 +1527,36 @@ QModelIndex ComicModel::getIndexFromId(quint64 id) return index(i, 0); } +ComicRowId ComicModel::rowId(const QModelIndex &index) const +{ + return rowIdOf(_data.at(index.row())); +} + +QModelIndex ComicModel::indexOf(const ComicRowId &rowId) const +{ + for (int row = 0; row < _data.size(); row++) { + if (rowIdOf(_data.at(row)) == rowId) + return index(row, 0); + } + return { }; +} + +QModelIndexList ComicModel::indexesOf(const QList &rowIds) const +{ + QHash rows; + rows.reserve(_data.size()); + for (int row = _data.size() - 1; row >= 0; row--) + rows.insert(rowIdOf(_data.at(row)), row); + + QModelIndexList indexes; + indexes.reserve(rowIds.size()); + for (const auto &rowId : rowIds) { + const auto row = rows.find(rowId); + indexes.append(row == rows.end() ? QModelIndex() : index(*row, 0)); + } + return indexes; +} + // TODO completely inefficiently QList ComicModel::getIndexesFromIds(const QList &comicIds) { @@ -1142,15 +1585,18 @@ void ComicModel::finishTransaction() void ComicModel::removeInTransaction(int row) { + const auto *rowFile = rowComic(_data.at(row)); + if (rowFile == nullptr) + return; + auto dbTransaction = QSqlDatabase::database(_databaseConnection); bool found; - ComicDB c = DBHelper::loadComic(_data.at(row)->data(ComicModel::Id).toULongLong(), dbTransaction, found); + ComicDB c = DBHelper::loadComic(rowFile->file.id.value(), dbTransaction, found); DBHelper::removeFromDB(&c, dbTransaction); beginRemoveRows(QModelIndex(), row, row); removeRow(row); - delete _data.at(row); _data.removeAt(row); endRemoveRows(); @@ -1166,19 +1612,19 @@ void ComicModel::reload() { switch (mode) { case Folder: - takeUpdatedData(createFolderModelData(sourceId, _databasePath), defaultFolderContentSortFunction); + takeUpdatedData(createFolderModelData(sourceId, _databasePath), ComicItems::folderLessThan); break; case Favorites: setupFavoritesModelData(_databasePath); // TODO we need a comparator break; case Reading: - takeUpdatedData(createReadingModelData(_databasePath), [](const ComicItem *c1, const ComicItem *c2) { - return c1->data(ComicModel::LastTimeOpened).toDateTime() > c2->data(ComicModel::LastTimeOpened).toDateTime(); + takeUpdatedData(createReadingModelData(_databasePath), [](const ComicItem &c1, const ComicItem &c2) { + return rowInfo(c1)->lastTimeOpened.toLongLong() > rowInfo(c2)->lastTimeOpened.toLongLong(); }); break; case Recent: - takeUpdatedData(createRecentModelData(_databasePath), [](const ComicItem *c1, const ComicItem *c2) { - return c1->data(ComicModel::Added).toDateTime() > c2->data(ComicModel::Added).toDateTime(); + takeUpdatedData(createRecentModelData(_databasePath), [](const ComicItem &c1, const ComicItem &c2) { + return rowInfo(c1)->added.toLongLong() > rowInfo(c2)->added.toLongLong(); }); break; case Label: @@ -1190,6 +1636,9 @@ void ComicModel::reload() case SearchResult: // TODO: reload search results, we don't have a way to recreate the query in this class break; + case SmartList: + takeUpdatedData(createSmartListModelData(sourceId, _databasePath), ComicItems::searchLessThan); + break; } } @@ -1200,53 +1649,71 @@ void ComicModel::remove(int row) void ComicModel::reload(const ComicDB &comic) { - int row = 0; - bool found = false; - const auto ¤tData = _data; - for (auto *item : currentData) { - if (item->data(ComicModel::Id).toULongLong() == comic.id) { - found = true; - item->setData(ComicModel::ReadColumn, comic.info.read); - item->setData(ComicModel::CurrentPage, comic.info.currentPage); - item->setData(ComicModel::HasBeenOpened, true); - break; - } - row++; - } - if (found) - emit dataChanged(index(row, ReadColumn), index(row, HasBeenOpened), QVector() << ReadColumnRole << CurrentPageRole << HasBeenOpenedRole); + updateRowsOf({ comic.info.id }, [&comic](ComicInfo &info) { + info.read = comic.info.read; + info.currentPage = comic.info.currentPage; + info.hasBeenOpened = true; }, ReadColumn, HasBeenOpened, { ReadColumnRole, CurrentPageRole, HasBeenOpenedRole }); } void ComicModel::resetComicRating(const QModelIndex &mi) { - ComicDB comic = getComic(mi); + const auto *info = rowInfo(_data.at(mi.row())); + if (info == nullptr) + return; + const auto id = info->id; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - comic.info.rating = 0; - _data[mi.row()]->setData(ComicModel::Rating, 0); - DBHelper::update(&(comic.info), db); + if (auto info = DBHelper::loadComicInfo(id, db)) { + info->rating = 0; + DBHelper::update(&*info, db); + } - emit dataChanged(mi, mi); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + + updateRowsOf({ id }, [](ComicInfo &info) { info.rating = 0; }, 0, columnCount() - 1, { }); } -void ComicModel::notifyCoverChange(const ComicDB &comic) +void ComicModel::notifyCoverChange(const QList &ids) { - auto it = std::find_if(_data.begin(), _data.end(), [comic](ComicItem *item) { return item->data(ComicModel::Id).toULongLong() == comic.id; }); - if (it == _data.end()) - return; - // Keep cover changes non-structural. Removing and reinserting the row makes // views adjust their selection and scroll position before the edit refresh can // capture them. Changing the URL also makes QML reload the image even though // the underlying cover file path is unchanged. - ++coverRevisions[comic.id]; - const auto itemIndex = std::distance(_data.begin(), it); - emit dataChanged(index(itemIndex, 0), index(itemIndex, columnCount() - 1), { CoverPathRole }); + coverFileExistence.clear(); + for (const auto id : ids) + ++coverRevisions[id.value()]; + updateRowsOf(QSet(ids.cbegin(), ids.cend()), [](ComicInfo &) { }, 0, columnCount() - 1, { CoverPathRole }); +} + +void ComicModel::notifyPlaceholderCoverChange(const QHash> &coverKeys) +{ + if (coverKeys.isEmpty()) + return; + coverFileExistence.clear(); + QList rows; + for (int row = 0; row < _data.size(); row++) { + auto *placeholder = rowPlaceholder(_data[row]); + if (placeholder == nullptr) + continue; + const auto key = coverKeys.constFind(placeholder->id); + if (key == coverKeys.cend()) + continue; + placeholder->coverKey = *key; + rows.append(row); + } + emitRowRuns(rows, 0, columnCount() - 1, { CoverPathRole }); +} + +bool ComicModel::coverFileExists(const QString &path) const +{ + auto known = coverFileExistence.constFind(path); + if (known == coverFileExistence.cend()) + known = coverFileExistence.insert(path, QFileInfo::exists(path)); + return *known; } QUrl ComicModel::getCoverUrlPathForComicHash(const QString &hash) const @@ -1275,41 +1742,49 @@ void ComicModel::addComicsToFavorites(const QList &comicsList) QList comicIds; comicIds.reserve(comics.size()); for (const auto &comic : std::as_const(comics)) - comicIds.append(comic.id); + comicIds.append(comic.file.id.value()); emit favoritesChanged(comicIds); } -void ComicModel::addComicsToLabel(const QList &comicIds, qulonglong labelId) +void ComicModel::addComicsToLabel(const QList &comicsList, qulonglong labelId) { - addComicsToLabel(getIndexesFromIds(comicIds), labelId); + QList rows; + rows.reserve(comicsList.size()); + for (const auto &index : comicsList) + rows.append(rowIdOf(_data.at(index.row()))); + + addComicsToLabel(rows, labelId); } -void ComicModel::addComicsToLabel(const QList &comicsList, qulonglong labelId) +void ComicModel::addComicsToLabel(const QList &rows, qulonglong labelId) { - QList comics = getComics(comicsList); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - DBHelper::insertComicsInLabel(comics, labelId, db); + DBHelper::appendRowsToLabel(LabelId(labelId), rows, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); } -void ComicModel::addComicsToReadingList(const QList &comicIds, qulonglong readingListId) +void ComicModel::addComicsToReadingList(const QList &comicsList, qulonglong readingListId) { - addComicsToReadingList(getIndexesFromIds(comicIds), readingListId); + QList rows; + rows.reserve(comicsList.size()); + for (const auto &index : comicsList) + rows.append(rowIdOf(_data.at(index.row()))); + + addComicsToReadingList(rows, readingListId); } -void ComicModel::addComicsToReadingList(const QList &comicsList, qulonglong readingListId) +void ComicModel::addComicsToReadingList(const QList &rows, qulonglong readingListId) { - QList comics = getComics(comicsList); QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - DBHelper::insertComicsInReadingList(comics, readingListId, db); + DBHelper::appendRowsToReadingList(ReadingListId(readingListId), rows, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -1330,7 +1805,7 @@ void ComicModel::deleteComicsFromFavorites(const QList &comicsList) QList comicIds; comicIds.reserve(comics.size()); for (const auto &comic : std::as_const(comics)) - comicIds.append(comic.id); + comicIds.append(comic.file.id.value()); emit favoritesChanged(comicIds); if (mode == Favorites) @@ -1370,45 +1845,116 @@ void ComicModel::deleteComicsFromSpecialList(const QList &comicsLis } } -void ComicModel::deleteComicsFromLabel(const QList &comicsList, qulonglong labelId) +// a reading list also shows the rows of its child lists, so each row is removed from the list that holds it +void ComicModel::deleteComicsFromLists(const QList &comicsList) { - QList comics = getComics(comicsList); + QSet rows; + QMap, QList> entryIdsByList; + for (const auto &index : comicsList) { + if (const auto *entry = rowEntry(_data.at(index.row())); entry != nullptr && !rows.contains(index.row())) { + rows.insert(index.row()); + entryIdsByList[{ entry->id.listKind, entry->listId }].append(entry->id); + } + } + + bool removed = true; + QStringList placeholderCovers; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - DBHelper::deleteComicsFromLabel(comics, labelId, db); + for (auto it = entryIdsByList.cbegin(); it != entryIdsByList.cend(); ++it) { + const auto [listKind, listId] = it.key(); + std::optional covers; + switch (listKind) { + case ListKind::ReadingList: + covers = DBHelper::removeReadingListEntries(ReadingListId(listId), it.value(), db); + break; + case ListKind::Label: + covers = DBHelper::removeLabelEntries(LabelId(listId), it.value(), db); + break; + } + if (covers) + placeholderCovers.append(*covers); + else + removed = false; + } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + DBHelper::removePlaceholderCovers(_databasePath, placeholderCovers); - deleteComicsFromModel(comicsList); + if (!removed) { + reload(); + return; + } + + QList sortedRows(rows.begin(), rows.end()); + std::sort(sortedRows.begin(), sortedRows.end()); + QList removedRows; + for (const auto row : std::as_const(sortedRows)) + removedRows.append(index(row, 0)); + deleteComicsFromModel(removedRows); } -void ComicModel::deleteComicsFromReadingList(const QList &comicsList, qulonglong readingListId) +void ComicModel::removeMissingComicsFromFolder(const QList &comicsList) { - QList comics = getComics(comicsList); + QSet rows; + QMap> placeholderIdsByFolder; + for (const auto &index : comicsList) { + if (const auto *row = std::get_if(&_data.at(index.row())); row != nullptr && !rows.contains(index.row())) { + rows.insert(index.row()); + placeholderIdsByFolder[row->folderId].append(row->placeholder.id); + } + } + + bool removed = true; + QStringList placeholderCovers; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - - DBHelper::deleteComicsFromReadingList(comics, readingListId, db); + for (auto it = placeholderIdsByFolder.cbegin(); it != placeholderIdsByFolder.cend(); ++it) { + if (const auto covers = DBHelper::removePlaceholdersFromFolder(it.value(), it.key(), db)) + placeholderCovers.append(*covers); + else + removed = false; + } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + DBHelper::removePlaceholderCovers(_databasePath, placeholderCovers); + + if (!removed) { + reload(); + return; + } - deleteComicsFromModel(comicsList); + QList sortedRows(rows.begin(), rows.end()); + std::sort(sortedRows.begin(), sortedRows.end()); + QList removedRows; + for (const auto row : std::as_const(sortedRows)) + removedRows.append(index(row, 0)); + deleteComicsFromModel(removedRows); } +// one removal for each run of consecutive rows, from the last run to the first void ComicModel::deleteComicsFromModel(const QList &comicsList) { - QListIterator it(comicsList); - it.toBack(); - while (it.hasPrevious()) { - int row = it.previous().row(); - beginRemoveRows(QModelIndex(), row, row); - _data.removeAt(row); + QList rows; + rows.reserve(comicsList.size()); + for (const auto &index : comicsList) + rows.append(index.row()); + std::sort(rows.begin(), rows.end()); + rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); + + for (auto last = rows.size() - 1; last >= 0;) { + auto first = last; + while (first > 0 && rows.at(first - 1) == rows.at(first) - 1) + first--; + beginRemoveRows(QModelIndex(), rows.at(first), rows.at(last)); + _data.remove(rows.at(first), rows.at(last) - rows.at(first) + 1); endRemoveRows(); + last = first - 1; } if (_data.isEmpty()) @@ -1422,7 +1968,8 @@ bool ComicModel::isFavorite(const QModelIndex &index) { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - isFavorite = DBHelper::isFavoriteComic(_data[index.row()]->data(Id).toLongLong(), db); + const auto *comic = rowComic(_data.at(index.row())); + isFavorite = comic != nullptr && DBHelper::isFavoriteComic(comic->file.id.value(), db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -1455,20 +2002,25 @@ void ComicModel::setRecentRange(int days) void ComicModel::updateRating(int rating, QModelIndex mi) { - ComicDB comic = getComic(mi); + const auto *info = rowInfo(_data.at(mi.row())); + if (info == nullptr) + return; + const auto id = info->id; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); // TODO optimize update - comic.info.rating = rating; - _data[mi.row()]->setData(ComicModel::Rating, rating); - DBHelper::update(&(comic.info), db); + if (auto info = DBHelper::loadComicInfo(id, db)) { + info->rating = rating; + DBHelper::update(&*info, db); + } - emit dataChanged(mi, mi); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + + updateRowsOf({ id }, [rating](ComicInfo &info) { info.rating = rating; }, 0, columnCount() - 1, { }); } QString ComicModel::localizedDate(const QString &dbDate) const diff --git a/YACReaderLibrary/db/comic_model.h b/YACReaderLibrary/db/comic_model.h index c996bd93d..274996e82 100644 --- a/YACReaderLibrary/db/comic_model.h +++ b/YACReaderLibrary/db/comic_model.h @@ -1,6 +1,8 @@ #ifndef TABLEMODEL_H #define TABLEMODEL_H +#include "comic_item.h" +#include "comic_record.h" #include "yacreader_global.h" #include @@ -11,9 +13,7 @@ #include #include -class ComicDB; - -class ComicItem; +#include using namespace YACReader; @@ -46,6 +46,7 @@ class ComicModel : public QAbstractItemModel enum CalculatedColumns { Size = Columns::StoryArc + 1, + State, }; enum Roles { @@ -72,6 +73,11 @@ class ComicModel : public QAbstractItemModel SeriesRole, VolumeRole, StoryArcRole, + KindRole, + AvailableRole, + LastKnownNameRole, + ListItemIdRole, + ListIdRole, }; enum Mode { @@ -81,7 +87,8 @@ class ComicModel : public QAbstractItemModel Recent, Label, ReadingList, - SearchResult + SearchResult, + SmartList }; public: @@ -106,35 +113,51 @@ class ComicModel : public QAbstractItemModel void setupFolderModelData(unsigned long long int parentFolder, const QString &databasePath); void setupLabelModelData(unsigned long long int parentLabel, const QString &databasePath); void setupReadingListModelData(unsigned long long int parentReadingList, const QString &databasePath); + // a search text that cannot be read gives no rows + void setupSmartListModelData(unsigned long long int smartListId, const QString &databasePath); void setupFavoritesModelData(const QString &databasePath); void setupReadingModelData(const QString &databasePath); void setupRecentModelData(const QString &databasePath); // Métodos de conveniencia QStringList getPaths(const QString &_source); - QString getComicPath(QModelIndex mi); + QList getUnavailableList() const; + QString getComicPath(QModelIndex mi); // empty for a row without a file QString getCurrentPath() { return QString(_databasePath).remove("/.yacreaderlibrary"); } - ComicDB getComic(const QModelIndex &mi); //--> para la edición - // ComicDB getComic(int row); + // the comic of a row with a file, loaded again; nothing for a deleted comic + std::optional getComic(const QModelIndex &mi); + // from the row, without a query + ComicRecord record(const QModelIndex &index) const; + // nothing for a row that is not in a list + std::optional listEntry(const QModelIndex &index) const; QVector getReadList(); - QVector setAllComicsRead(YACReaderComicReadStatus readStatus); - QList getComics(QList list); //--> recupera la información común a los comics seleccionados - QList getAllComics(); - QModelIndex getIndexFromId(quint64 id); + // the comics of the rows that have a file, loaded again; rows without a file are skipped + QList getComics(QList list); + bool supports(const QModelIndex &index, ComicAction action) const; + bool hasUnavailableRows() const; + QModelIndexList supportedIndexes(const QModelIndexList &indexes, ComicAction action) const; + // each comic_info of the rows once, loaded again, in row order + void deleteMetadata(const QModelIndexList &indexes); + // from the rows, without a query: the rows are as fresh as the last load + std::optional firstUnreadComic() const; + QModelIndex getIndexFromId(quint64 id); // the first row with that file + ComicRowId rowId(const QModelIndex &index) const; + // an invalid index when the row is not in the model + QModelIndex indexOf(const ComicRowId &rowId) const; + QModelIndexList indexesOf(const QList &rowIds) const; QList getIndexesFromIds(const QList &comicIds); - // setcomicInfo(QModelIndex & mi); --> inserta en la base datos - // setComicInfoForAllComics(); --> inserta la información común a todos los cómics de una sola vez. - // setComicInfoForSelectedComis(QList list); -->inserta la información común para los comics seleccionados QVector setComicsRead(QList list, YACReaderComicReadStatus read); void setComicsType(QList list, FileType type); - qint64 asignNumbers(QList list, int startingNumber); + ComicRowId asignNumbers(QList list, int startingNumber); // void remove(ComicDB * comic, int row); void removeInTransaction(int row); void reloadContinueReading(); void reload(); void reload(const ComicDB &comic); void resetComicRating(const QModelIndex &mi); - void notifyCoverChange(const ComicDB &comic); + void notifyCoverChange(const QList &ids); + // the rows of each placeholder show its new cover + void notifyPlaceholderCoverChange(const QHash> &coverKeys); Q_INVOKABLE QUrl getCoverUrlPathForComicHash(const QString &hash) const; @@ -145,8 +168,9 @@ class ComicModel : public QAbstractItemModel void deleteComicsFromFavorites(const QList &comicsList); void deleteComicsFromReading(const QList &comicsList); void deleteComicsFromSpecialList(const QList &comicsList, qulonglong specialListId); - void deleteComicsFromLabel(const QList &comicsList, qulonglong labelId); - void deleteComicsFromReadingList(const QList &comicsList, qulonglong readingListId); + void deleteComicsFromLists(const QList &comicsList); + // the missing comics of the folder; the others are skipped + void removeMissingComicsFromFolder(const QList &comicsList); void deleteComicsFromModel(const QList &comicsList); @@ -167,28 +191,32 @@ public slots: void updateRating(int rating, QModelIndex mi); void addComicsToFavorites(const QList &comicIds); - void addComicsToLabel(const QList &comicIds, qulonglong labelId); - void addComicsToReadingList(const QList &comicIds, qulonglong readingListId); + void addComicsToLabel(const QList &rows, qulonglong labelId); + void addComicsToReadingList(const QList &rows, qulonglong readingListId); - void setModelData(QList *data, const QString &databasePath); + void setModelData(QList *data, const QString &databasePath); protected: private: - QHash coverRevisions; - QList createModelData(QSqlQuery &sqlquery) const; - QList createModelDataForList(QSqlQuery &sqlquery) const; - - QList createFolderModelData(unsigned long long parentLabel, const QString &databasePath) const; - QList createLabelModelData(unsigned long long parentLabel, const QString &databasePath) const; - QList createReadingListData(unsigned long long parentReadingList, const QString &databasePath, bool &enableResorting) const; - QList createFavoritesModelData(const QString &databasePath) const; - QList createReadingModelData(const QString &databasePath) const; - QList createRecentModelData(const QString &databasePath) const; - - void takeData(const QList &data); - void takeUpdatedData(const QList &updatedData, std::function comparator); - ComicDB _getComic(const QModelIndex &mi); - QList _data; + QHash coverRevisions; // by comic_info id + // the cover of a row without a file may be gone; checked once per path, cleared when the rows or a cover change + bool coverFileExists(const QString &path) const; + mutable QHash coverFileExistence; + + QList createFolderModelData(unsigned long long parentLabel, const QString &databasePath) const; + QList createLabelModelData(unsigned long long parentLabel, const QString &databasePath) const; + QList createReadingListData(unsigned long long parentReadingList, const QString &databasePath, bool &enableResorting) const; + QList createFavoritesModelData(const QString &databasePath) const; + QList createReadingModelData(const QString &databasePath) const; + QList createRecentModelData(const QString &databasePath) const; + QList createSmartListModelData(unsigned long long smartListId, const QString &databasePath) const; + + void takeData(const QList &data); + void takeUpdatedData(const QList &updatedData, std::function comparator); + void updateRowsOf(const QSet &ids, const std::function &change, int firstColumn, int lastColumn, const QList &roles); + // one dataChanged for each run of consecutive rows; `rows` is ascending + void emitRowRuns(const QList &rows, int firstColumn, int lastColumn, const QList &roles); + QList _data; QString _databasePath; QString _databaseConnection; @@ -204,7 +232,6 @@ public slots: signals: void isEmpty(); void favoritesChanged(const QList &comicIds); - void searchNumResults(int); void resortedIndexes(QList); void newSelectedIndex(const QModelIndex &); }; diff --git a/YACReaderLibrary/db/comic_query_result_processor.cpp b/YACReaderLibrary/db/comic_query_result_processor.cpp index 65d289119..2c7c48cd8 100644 --- a/YACReaderLibrary/db/comic_query_result_processor.cpp +++ b/YACReaderLibrary/db/comic_query_result_processor.cpp @@ -1,7 +1,6 @@ #include "comic_query_result_processor.h" #include "comic_item.h" -#include "comic_model.h" #include "data_base_management.h" #include "qnaturalsorting.h" #include "search_query.h" @@ -24,7 +23,7 @@ void YACReader::ComicQueryResultProcessor::createModelData(const QString &filter try { auto query = comicsSearchQuery(db, filter); - auto data = modelData(query); + auto data = new QList(ComicItems::sortedForSearch(ComicItems::fromQuery(query))); emit newData(data, databasePath); } catch (const std::exception &e) { @@ -37,26 +36,3 @@ void YACReader::ComicQueryResultProcessor::createModelData(const QString &filter QSqlDatabase::removeDatabase(connectionName); }); } - -QList *YACReader::ComicQueryResultProcessor::modelData(QSqlQuery &sqlquery) -{ - auto list = new QList(); - - int numColumns = sqlquery.record().count(); - - while (sqlquery.next()) { - QList data; - - for (int i = 0; i < numColumns; i++) - data << sqlquery.value(i); - - list->append(new ComicItem(data)); - } - - std::sort(list->begin(), list->end(), [](const ComicItem *c1, const ComicItem *c2) { - return comicNumberLessThan(c1->data(ComicModel::Number), c1->data(ComicModel::FileName).toString(), - c2->data(ComicModel::Number), c2->data(ComicModel::FileName).toString()); - }); - - return list; -} diff --git a/YACReaderLibrary/db/comic_query_result_processor.h b/YACReaderLibrary/db/comic_query_result_processor.h index 103b522e0..6cf7f03cd 100644 --- a/YACReaderLibrary/db/comic_query_result_processor.h +++ b/YACReaderLibrary/db/comic_query_result_processor.h @@ -1,13 +1,12 @@ #ifndef COMIC_QUERY_RESULT_PROCESSOR_H #define COMIC_QUERY_RESULT_PROCESSOR_H +#include "comic_item.h" #include "concurrent_queue.h" #include #include -class ComicItem; - namespace YACReader { class ComicQueryResultProcessor : public QObject @@ -19,12 +18,10 @@ class ComicQueryResultProcessor : public QObject public slots: void createModelData(const QString &filter, const QString &databasePath); signals: - void newData(QList *, const QString &); + void newData(QList *, const QString &); private: ConcurrentQueue querySearchQueue; - - static QList *modelData(QSqlQuery &sqlquery); }; }; diff --git a/YACReaderLibrary/db/comic_record.h b/YACReaderLibrary/db/comic_record.h new file mode 100644 index 000000000..ddbc011c5 --- /dev/null +++ b/YACReaderLibrary/db/comic_record.h @@ -0,0 +1,103 @@ +#ifndef COMIC_RECORD_H +#define COMIC_RECORD_H + +#include "comic_db.h" +#include "list_entry.h" +#include "overloaded.h" +#include "placeholder.h" + +#include + +#include +#include +#include + +// what the info panel and the dialogs show: the comic without its list or location data +namespace ComicRecordCase { + +struct Comic { + ComicDB comic; +}; + +struct DeletedComic { + ComicInfo info; + std::optional lastKnownFile; +}; + +struct Placeholder { + ::Placeholder placeholder; +}; + +} + +using ComicRecord = std::variant; + +// the descriptive metadata, which every case has +inline const DescriptiveComicMetadata &recordMetadata(const ComicRecord &record) +{ + return std::visit(overloaded { + [](const ComicRecordCase::Comic &value) -> const DescriptiveComicMetadata & { return value.comic.info; }, + [](const ComicRecordCase::DeletedComic &value) -> const DescriptiveComicMetadata & { return value.info; }, + [](const ComicRecordCase::Placeholder &value) -> const DescriptiveComicMetadata & { return value.placeholder; }, + }, + record); +} + +// nullptr for a placeholder, which has no comic_info +inline const ComicInfo *recordInfo(const ComicRecord &record) +{ + return std::visit(overloaded { + [](const ComicRecordCase::Comic &value) -> const ComicInfo * { return &value.comic.info; }, + [](const ComicRecordCase::DeletedComic &value) -> const ComicInfo * { return &value.info; }, + [](const ComicRecordCase::Placeholder &) -> const ComicInfo * { return nullptr; }, + }, + record); +} + +inline ComicInfo *recordInfo(ComicRecord &record) +{ + return const_cast(recordInfo(std::as_const(record))); +} + +inline const Placeholder *recordPlaceholder(const ComicRecord &record) +{ + const auto *value = std::get_if(&record); + return value != nullptr ? &value->placeholder : nullptr; +} + +// nullptr when the comic has no file +inline const ComicDB *recordComic(const ComicRecord &record) +{ + const auto *value = std::get_if(&record); + return value != nullptr ? &value->comic : nullptr; +} + +inline ComicDB *recordComic(ComicRecord &record) +{ + auto *value = std::get_if(&record); + return value != nullptr ? &value->comic : nullptr; +} + +// the file name, or the last known one of a deleted comic; only for display and searches +inline QString recordFileName(const ComicRecord &record) +{ + return std::visit(overloaded { + [](const ComicRecordCase::Comic &value) { return QFileInfo(value.comic.file.path).fileName(); }, + [](const ComicRecordCase::DeletedComic &value) { return value.lastKnownFile ? value.lastKnownFile->name : QString(); }, + [](const ComicRecordCase::Placeholder &) { return QString(); }, + }, + record); +} + +// the path in the library, or the last known one of a deleted comic; only for display and searches +inline QString recordPath(const ComicRecord &record) +{ + return std::visit(overloaded { + [](const ComicRecordCase::Comic &value) { return value.comic.file.path; }, + [](const ComicRecordCase::DeletedComic &value) { return value.lastKnownFile ? value.lastKnownFile->path : QString(); }, + [](const ComicRecordCase::Placeholder &) { return QString(); }, + }, + record); +} + +#endif diff --git a/YACReaderLibrary/db/data_base_management.cpp b/YACReaderLibrary/db/data_base_management.cpp index ee4adfc55..e4502aef1 100644 --- a/YACReaderLibrary/db/data_base_management.cpp +++ b/YACReaderLibrary/db/data_base_management.cpp @@ -2,6 +2,7 @@ #include "QsLog.h" #include "db_helper.h" +#include "descriptive_comic_metadata.h" #include "initial_comic_info_extractor.h" #include @@ -15,7 +16,11 @@ #ifdef Q_OS_WIN #include #else +#include #include +#include +#include +#include #endif using namespace YACReader; @@ -23,6 +28,25 @@ using namespace YACReader; namespace { const auto backupTimestampFormat = QStringLiteral("yyyyMMdd-HHmmss"); +// QLockFile keeps a native lock on its file for as long as its owner runs +bool lockFileIsHeld(const QString &path) +{ +#ifdef Q_OS_WIN + const auto handle = CreateFileW(reinterpret_cast(QDir::toNativeSeparators(path).utf16()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) + return GetLastError() == ERROR_SHARING_VIOLATION; + CloseHandle(handle); + return false; +#else + const auto fd = ::open(QFile::encodeName(path).constData(), O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + const auto held = ::flock(fd, LOCK_SH | LOCK_NB) == -1 && errno == EWOULDBLOCK; + ::close(fd); + return held; +#endif +} + QString backupReasonName(DatabaseBackupReason reason) { switch (reason) { @@ -275,20 +299,50 @@ LibraryMaintenanceLock::~LibraryMaintenanceLock() = default; bool LibraryMaintenanceLock::tryLockFile(QLockFile &lock, bool removeStaleLock) { + const auto path = lock.fileName(); + // recovery cannot remove a folder, so it must never be reported as contention + if (QFileInfo(path).isDir()) { + lastError = QLockFile::UnknownError; + failedLockPath = path; + return false; + } if (lock.tryLock()) return true; - if (removeStaleLock && lock.removeStaleLockFile() && lock.tryLock()) + lastError = lock.error(); + failedLockPath = path; + if (lastError != QLockFile::LockFailedError) + return false; + const auto record = captureLockInfo(lock); + if (!removeStaleLock || currentHolderIsRunning) + return false; + if (expectedLocks.contains(path) && expectedLocks.value(path) != record) { + recoveryError = QCoreApplication::translate("LibraryMaintenanceLock", "The library lock owner changed. Retry the operation and review the new owner before recovering it."); + return false; + } + if (!lock.removeStaleLockFile()) { + recoveryError = QCoreApplication::translate("LibraryMaintenanceLock", "Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry.").arg(path); + return false; + } + if (lock.tryLock()) { + lastError = QLockFile::NoError; + failedLockPath.clear(); return true; - captureLockInfo(lock); - failedLockPath = lock.fileName(); + } + lastError = lock.error(); + if (lastError == QLockFile::LockFailedError) + captureLockInfo(lock); return false; } bool LibraryMaintenanceLock::tryLock(bool removeStaleLock) { + expectedLocks = removeStaleLock ? observedLocks : QHash(); + observedLocks.clear(); + recoveryError.clear(); failedLockPath.clear(); + lastError = QLockFile::NoError; currentHolderInfo.clear(); - currentHolderIsRunningLocally = false; + currentHolderIsRunning = false; if (!tryLockFile(*maintenanceLock, removeStaleLock)) return false; if (!tryLockFile(*legacyRepairLock, removeStaleLock)) { @@ -298,21 +352,47 @@ bool LibraryMaintenanceLock::tryLock(bool removeStaleLock) return true; } -void LibraryMaintenanceLock::captureLockInfo(QLockFile &lock) +QByteArray LibraryMaintenanceLock::captureLockInfo(QLockFile &lock) { + QFile file(lock.fileName()); + const auto record = file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray(); + observedLocks.insert(lock.fileName(), record); + currentHolderInfo.clear(); + // a record without its native lock is stale, even if QLockFile could not remove it + currentHolderIsRunning = lockFileIsHeld(lock.fileName()); + qint64 pid = 0; QString hostname; QString appname; - if (lock.getLockInfo(&pid, &hostname, &appname)) { + if (lock.getLockInfo(&pid, &hostname, &appname)) currentHolderInfo = QString("%1 (PID %2) on %3").arg(appname).arg(pid).arg(hostname.isEmpty() ? QString("unknown host") : hostname); - currentHolderIsRunningLocally = !hostname.isEmpty() && hostname == QSysInfo::machineHostName(); - } + return record; } QString LibraryMaintenanceLock::errorString() const { - return QString("Another maintenance operation is using this library (%1)%2") - .arg(failedLockPath, currentHolderInfo.isEmpty() ? QString() : QString(": %1").arg(currentHolderInfo)); + if (!recoveryError.isEmpty()) + return recoveryError; + if (lastError == QLockFile::NoError) + return { }; + if (QFileInfo(failedLockPath).isDir()) + return QCoreApplication::translate("LibraryMaintenanceLock", "A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying.").arg(failedLockPath); + if (lastError == QLockFile::PermissionError) + return QCoreApplication::translate("LibraryMaintenanceLock", "Unable to create the library lock file: %1. Check write permissions for the library data folder.").arg(failedLockPath); + if (!QFileInfo(failedLockPath).absoluteDir().exists()) + return QCoreApplication::translate("LibraryMaintenanceLock", "The library data folder is missing: %1. Restore the folder or recreate the library before updating it.") + .arg(QFileInfo(failedLockPath).absolutePath()); + if (lastError != QLockFile::LockFailedError) + return QCoreApplication::translate("LibraryMaintenanceLock", "Unable to create the library lock file: %1. Check available disk space and access to the library data folder.").arg(failedLockPath); + const auto holder = currentHolderInfo.isEmpty() ? QString() : QString(": %1").arg(currentHolderInfo); + if (!currentHolderIsRunning) + return QCoreApplication::translate("LibraryMaintenanceLock", "The library has a maintenance lock whose owner could not be verified (%1)%2").arg(failedLockPath, holder); + return QCoreApplication::translate("LibraryMaintenanceLock", "Another maintenance operation is using this library (%1)%2").arg(failedLockPath, holder); +} + +QLockFile::LockError LibraryMaintenanceLock::error() const +{ + return lastError; } QString LibraryMaintenanceLock::holderInfo() const @@ -320,9 +400,27 @@ QString LibraryMaintenanceLock::holderInfo() const return currentHolderInfo; } -bool LibraryMaintenanceLock::holderIsRunningLocally() const +bool LibraryMaintenanceLock::holderIsRunning() const +{ + return currentHolderIsRunning; +} + +QString LibraryMaintenanceLock::recoveryToken() const +{ + if (lastError != QLockFile::LockFailedError || observedLocks.isEmpty()) + return { }; + QStringList paths = observedLocks.keys(); + paths.sort(); + QByteArray serialized; + QDataStream stream(&serialized, QIODevice::WriteOnly); + for (const auto &path : paths) + stream << path << observedLocks.value(path); + return QString::fromLatin1(QCryptographicHash::hash(serialized, QCryptographicHash::Sha256).toHex()); +} + +LibraryMaintenanceLockFailure LibraryMaintenanceLock::failure() const { - return currentHolderIsRunningLocally; + return { lastError, currentHolderIsRunning, currentHolderInfo, recoveryToken() }; } static QString fields = "title," @@ -544,6 +642,9 @@ bool DataBaseManagement::createTables(QSqlDatabase &database) // 8.0> tables success = success && DataBaseManagement::createV8Tables(database); + + // One schema change for entries, placeholders, smart lists, and list folders. + success = success && DataBaseManagement::createV10_5Tables(database); } return success; @@ -742,9 +843,6 @@ bool DataBaseManagement::createV8Tables(QSqlDatabase &database) "FOREIGN KEY(comic_id) REFERENCES comic(id) ON DELETE CASCADE, " "PRIMARY KEY(label_id, comic_id))"); - QSqlQuery queryIndexComicLabel(database); - success = success && queryIndexComicLabel.exec("CREATE INDEX comic_label_ordering_index ON label (ordering)"); - // READING LIST QSqlQuery queryReadingList(database); success = success && queryReadingList.exec("CREATE TABLE reading_list (" @@ -757,9 +855,6 @@ bool DataBaseManagement::createV8Tables(QSqlDatabase &database) "manga BOOLEAN DEFAULT 0, " // TODO never used, replace with `type` "FOREIGN KEY(parentId) REFERENCES reading_list(id) ON DELETE CASCADE)"); - QSqlQuery queryIndexReadingList(database); - success = success && queryIndexReadingList.exec("CREATE INDEX reading_list_ordering_index ON label (ordering)"); - // COMIC READING LIST QSqlQuery queryComicReadingList(database); success = success && queryComicReadingList.exec("CREATE TABLE comic_reading_list (" @@ -770,9 +865,6 @@ bool DataBaseManagement::createV8Tables(QSqlDatabase &database) "FOREIGN KEY(comic_id) REFERENCES comic(id) ON DELETE CASCADE, " "PRIMARY KEY(reading_list_id, comic_id))"); - QSqlQuery queryIndexComicReadingList(database); - success = success && queryIndexComicReadingList.exec("CREATE INDEX comic_reading_list_ordering_index ON label (ordering)"); - // DEFAULT READING LISTS QSqlQuery queryDefaultReadingList(database); success = success && queryDefaultReadingList.exec("CREATE TABLE default_reading_list (" @@ -791,9 +883,6 @@ bool DataBaseManagement::createV8Tables(QSqlDatabase &database) "FOREIGN KEY(comic_id) REFERENCES comic(id) ON DELETE CASCADE," "PRIMARY KEY(default_reading_list_id, comic_id))"); - QSqlQuery queryIndexComicDefaultReadingList(database); - success = success && queryIndexComicDefaultReadingList.exec("CREATE INDEX comic_default_reading_list_ordering_index ON label (ordering)"); - // INSERT DEFAULT READING LISTS QSqlQuery queryInsertDefaultReadingList(database); // if(!queryInsertDefaultReadingList.prepare()) @@ -807,6 +896,183 @@ bool DataBaseManagement::createV8Tables(QSqlDatabase &database) return success; } +bool DataBaseManagement::createPlaceholderTable(QSqlDatabase &database) +{ + QStringList columns = { "id INTEGER PRIMARY KEY AUTOINCREMENT" }; + for (const auto &column : DescriptiveComicMetadata::columns()) { + columns.append(QString::fromLatin1(column.name) + " " + QString::fromLatin1(column.sqlType)); + } + columns.append("coverKey TEXT"); + + QSqlQuery queryPlaceholder(database); + return queryPlaceholder.exec("CREATE TABLE placeholder (" + columns.join(", ") + ")"); +} + +bool DataBaseManagement::createReadingListEntryTable(QSqlDatabase &database) +{ + bool success = true; + { + QSqlQuery queryReadingListEntry(database); + success = success && queryReadingListEntry.exec("CREATE TABLE reading_list_entry (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "reading_list_id INTEGER NOT NULL, " + "ordering INTEGER NOT NULL, " + "comic_info_id INTEGER, " + "placeholder_id INTEGER, " + "last_known_name TEXT, " + "last_known_path TEXT, " + // the CBL book of an imported entry, as in the file, and its format (xml or json) + "source TEXT, " + "source_format TEXT, " + "FOREIGN KEY(reading_list_id) REFERENCES reading_list(id) ON DELETE CASCADE, " + "FOREIGN KEY(comic_info_id) REFERENCES comic_info(id) ON DELETE RESTRICT, " + "FOREIGN KEY(placeholder_id) REFERENCES placeholder(id) ON DELETE RESTRICT, " + "CHECK ((comic_info_id IS NOT NULL AND placeholder_id IS NULL) " + "OR (comic_info_id IS NULL AND placeholder_id IS NOT NULL)))"); + + QSqlQuery queryIndexReadingListEntry(database); + success = success && queryIndexReadingListEntry.exec("CREATE INDEX reading_list_entry_ordering_index ON reading_list_entry (reading_list_id, ordering)"); + success = success && queryIndexReadingListEntry.exec("CREATE INDEX reading_list_entry_comic_info_index ON reading_list_entry (comic_info_id)"); + success = success && queryIndexReadingListEntry.exec("CREATE INDEX reading_list_entry_placeholder_index ON reading_list_entry (placeholder_id)"); + } + return success; +} + +bool DataBaseManagement::createLabelEntryTable(QSqlDatabase &database) +{ + bool success = true; + { + QSqlQuery queryLabelEntry(database); + success = success && queryLabelEntry.exec("CREATE TABLE label_entry (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "label_id INTEGER NOT NULL, " + "ordering INTEGER NOT NULL, " + "comic_info_id INTEGER, " + "placeholder_id INTEGER, " + "last_known_name TEXT, " + "last_known_path TEXT, " + "FOREIGN KEY(label_id) REFERENCES label(id) ON DELETE CASCADE, " + "FOREIGN KEY(comic_info_id) REFERENCES comic_info(id) ON DELETE RESTRICT, " + "FOREIGN KEY(placeholder_id) REFERENCES placeholder(id) ON DELETE RESTRICT, " + "CHECK ((comic_info_id IS NOT NULL AND placeholder_id IS NULL) " + "OR (comic_info_id IS NULL AND placeholder_id IS NOT NULL)))"); + + QSqlQuery queryIndexLabelEntry(database); + success = success && queryIndexLabelEntry.exec("CREATE INDEX label_entry_ordering_index ON label_entry (label_id, ordering)"); + success = success && queryIndexLabelEntry.exec("CREATE INDEX label_entry_comic_info_index ON label_entry (comic_info_id)"); + success = success && queryIndexLabelEntry.exec("CREATE INDEX label_entry_placeholder_index ON label_entry (placeholder_id)"); + } + return success; +} + +bool DataBaseManagement::createSmartListTable(QSqlDatabase &database) +{ + QSqlQuery query(database); + return query.exec("CREATE TABLE smart_list (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "name TEXT NOT NULL, " + "query TEXT NOT NULL, " + "ordering INTEGER NOT NULL)"); +} + +// deleting a folder deletes its folders and lists, and so the child lists and entries of its reading lists +bool DataBaseManagement::createListFolderTables(QSqlDatabase &database) +{ + bool success = true; + for (const auto &[folderTable, listTable] : { std::pair { "reading_list_folder", "reading_list" }, std::pair { "smart_list_folder", "smart_list" } }) { + QSqlQuery query(database); + success = success && query.exec(QString("CREATE TABLE %1 (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "parent_id INTEGER, " + "name TEXT NOT NULL, " + "ordering INTEGER NOT NULL DEFAULT 0, " + "FOREIGN KEY(parent_id) REFERENCES %1(id) ON DELETE CASCADE)") + .arg(folderTable)); + success = success && query.exec(QString("CREATE INDEX %1_parent_index ON %1 (parent_id)").arg(folderTable)); + success = success && addColumns(listTable, { QString("folder_id INTEGER REFERENCES %1(id) ON DELETE CASCADE").arg(folderTable) }, database); + success = success && query.exec(QString("CREATE INDEX %1_folder_index ON %1 (folder_id)").arg(listTable)); + } + return success; +} + +bool DataBaseManagement::createFolderPlaceholderTable(QSqlDatabase &database) +{ + QSqlQuery query(database); + return query.exec("CREATE TABLE folder_placeholder (" + "folder_id INTEGER NOT NULL, " + "placeholder_id INTEGER NOT NULL, " + "FOREIGN KEY(folder_id) REFERENCES folder(id) ON DELETE CASCADE, " + "FOREIGN KEY(placeholder_id) REFERENCES placeholder(id) ON DELETE RESTRICT, " + "PRIMARY KEY(folder_id, placeholder_id))") && + query.exec("CREATE INDEX folder_placeholder_placeholder_index ON folder_placeholder (placeholder_id)"); +} + +bool DataBaseManagement::createComicDeleteTrigger(QSqlDatabase &database) +{ + QSqlQuery queryComicDeleteTrigger(database); + return queryComicDeleteTrigger.exec("CREATE TRIGGER comic_delete_last_known_file AFTER DELETE ON comic " + "BEGIN " + "UPDATE reading_list_entry SET last_known_name = OLD.fileName, last_known_path = OLD.path " + "WHERE comic_info_id = OLD.comicInfoId; " + "UPDATE label_entry SET last_known_name = OLD.fileName, last_known_path = OLD.path " + "WHERE comic_info_id = OLD.comicInfoId; " + "END"); +} + +bool DataBaseManagement::createV10_5Tables(QSqlDatabase &database) +{ + bool success = true; + { + success = success && createPlaceholderTable(database); + success = success && createReadingListEntryTable(database); + success = success && createLabelEntryTable(database); + + // Rows whose list, label, comic or comic_info no longer exists are not copied. + QSqlQuery copyReadingListEntries(database); + success = success && copyReadingListEntries.exec("INSERT INTO reading_list_entry (reading_list_id, ordering, comic_info_id) " + "SELECT crl.reading_list_id, " + "ROW_NUMBER() OVER (PARTITION BY crl.reading_list_id ORDER BY crl.ordering IS NULL, crl.ordering, crl.rowid) - 1, " + "c.comicInfoId " + "FROM comic_reading_list crl " + "INNER JOIN reading_list rl ON rl.id = crl.reading_list_id " + "INNER JOIN comic c ON c.id = crl.comic_id " + "INNER JOIN comic_info ci ON ci.id = c.comicInfoId " + "ORDER BY crl.reading_list_id, crl.ordering IS NULL, crl.ordering, crl.rowid"); + + QSqlQuery copyLabelEntries(database); + success = success && copyLabelEntries.exec("INSERT INTO label_entry (label_id, ordering, comic_info_id) " + "SELECT cl.label_id, " + "ROW_NUMBER() OVER (PARTITION BY cl.label_id ORDER BY cl.ordering IS NULL, cl.ordering, cl.rowid) - 1, " + "c.comicInfoId " + "FROM comic_label cl " + "INNER JOIN label l ON l.id = cl.label_id " + "INNER JOIN comic c ON c.id = cl.comic_id " + "INNER JOIN comic_info ci ON ci.id = c.comicInfoId " + "ORDER BY cl.label_id, cl.ordering IS NULL, cl.ordering, cl.rowid"); + + QSqlQuery dropOldTables(database); + success = success && dropOldTables.exec("DROP TABLE comic_reading_list"); + success = success && dropOldTables.exec("DROP TABLE comic_label"); + + QSqlQuery queryIndexComicInfoId(database); + success = success && queryIndexComicInfoId.exec("CREATE INDEX comic_comic_info_id_index ON comic (comicInfoId)"); + + success = success && createComicDeleteTrigger(database); + + success = success && createSmartListTable(database); + success = success && createListFolderTables(database); + success = success && createFolderPlaceholderTable(database); + + // Upgrades run with foreign keys off, so the new constraints are checked here. + QSqlQuery checkReadingListEntries(database); + success = success && checkReadingListEntries.exec("PRAGMA foreign_key_check(reading_list_entry)") && !checkReadingListEntries.next(); + + QSqlQuery checkLabelEntries(database); + success = success && checkLabelEntries.exec("PRAGMA foreign_key_check(label_entry)") && !checkLabelEntries.next(); + } + return success; +} + void DataBaseManagement::exportComicsInfo(QString source, QString dest) { QString connectionName = ""; @@ -1327,6 +1593,11 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath, bool } } + QString recoveryError; + if (!recoverInterruptedRestore(libraryPath, &recoveryError, true)) { + QLOG_ERROR() << "Database upgrade recovery failed:" << recoveryError; + return false; + } QString libraryDatabasePath = LibraryPaths::libraryDatabasePath(libraryPath); QString backupError; if (!backupLibrary(libraryPath, DatabaseBackupReason::BeforeUpgrade, &backupError)) { @@ -1343,6 +1614,7 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath, bool const bool pre9_13 = compareVersions(oldVersion, "9.13.0") < 0; const bool pre9_14 = compareVersions(oldVersion, "9.14.0") < 0; const bool pre9_16 = compareVersions(oldVersion, "9.16.0") < 0; + const bool pre10_5 = compareVersions(oldVersion, "10.5.0") < 0; QString connectionName = ""; bool returnValue = true; @@ -1430,8 +1702,6 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath, bool selectQuery.prepare("SELECT id, hash FROM comic_info"); selectQuery.exec(); - db.transaction(); - QSqlQuery updateCoverInfo(db); updateCoverInfo.prepare("UPDATE comic_info SET coverSizeRatio = :coverSizeRatio WHERE id = :id"); @@ -1446,8 +1716,6 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath, bool updateCoverInfo.exec(); } - - db.commit(); } } @@ -1559,6 +1827,16 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath, bool } } + if (pre10_5) { + returnValue = returnValue && createV10_5Tables(db); + + // Libraries created before 10.5 have these ON label (ordering), duplicates of label_ordering_index. + QSqlQuery dropWrongIndexes(db); + for (const auto index : { "comic_label_ordering_index", "reading_list_ordering_index", "comic_reading_list_ordering_index", "comic_default_reading_list_ordering_index" }) { + returnValue = returnValue && dropWrongIndexes.exec(QString("DROP INDEX IF EXISTS %1").arg(index)); + } + } + if (returnValue) { QSqlQuery updateVersion(db); updateVersion.prepare("UPDATE db_info SET " @@ -1621,8 +1899,9 @@ bool DataBaseManagement::backupLibrary(const QString &libraryPath, DatabaseBacku } const auto reasonName = backupReasonName(reason); - const auto finalPath = destinationPath.isEmpty() - ? backupsDirectory.filePath(QString("library-%1-db-%2-%3.ydb").arg(QDateTime::currentDateTime().toString(backupTimestampFormat), sourceVersion, reasonName)) + const auto timestamp = QDateTime::currentDateTime().toString(backupTimestampFormat); + auto finalPath = destinationPath.isEmpty() + ? backupsDirectory.filePath(QString("library-%1-db-%2-%3.ydb").arg(timestamp, sourceVersion, reasonName)) : QDir::cleanPath(destinationPath); if (QFileInfo(finalPath).absoluteFilePath() == QFileInfo(databasePath).absoluteFilePath()) { if (error) @@ -1630,19 +1909,18 @@ bool DataBaseManagement::backupLibrary(const QString &libraryPath, DatabaseBacku return false; } - QString backupPath = finalPath; - if (!destinationPath.isEmpty()) { - QTemporaryFile temporary(finalPath + ".XXXXXX.tmp"); - temporary.setAutoRemove(false); - if (!temporary.open()) { - if (error) - *error = temporary.errorString(); - return false; - } - backupPath = temporary.fileName(); - temporary.close(); - temporary.remove(); + // VACUUM writes a private staging file. A failed/concurrent attempt must + // never remove a completed backup that happens to share its timestamp. + QTemporaryFile temporary(finalPath + ".XXXXXX.tmp"); + temporary.setAutoRemove(false); + if (!temporary.open()) { + if (error) + *error = temporary.errorString(); + return false; } + const auto backupPath = temporary.fileName(); + temporary.close(); + temporary.remove(); if (QFile::exists(backupPath)) { if (error) *error = QString("Backup file already exists: %1").arg(backupPath); @@ -1664,6 +1942,18 @@ bool DataBaseManagement::backupLibrary(const QString &libraryPath, DatabaseBacku QLOG_ERROR() << "Database backup replacement failed" << finalPath << (error ? *error : QString()); return false; } + if (destinationPath.isEmpty()) { + quint64 collision = 0; + while (!QFile::rename(backupPath, finalPath)) { + if (!QFile::exists(finalPath)) { + if (error) + *error = QString("Unable to install database backup: %1").arg(finalPath); + QFile::remove(backupPath); + return false; + } + finalPath = backupsDirectory.filePath(QString("library-%1-%2-db-%3-%4.ydb").arg(timestamp).arg(++collision, 6, 10, QLatin1Char('0')).arg(sourceVersion, reasonName)); + } + } QLOG_INFO() << "Database backup created" << finalPath; if (destinationPath.isEmpty()) @@ -1672,6 +1962,11 @@ bool DataBaseManagement::backupLibrary(const QString &libraryPath, DatabaseBacku } DatabaseRestoreResult DataBaseManagement::restoreLibrary(const QString &libraryPath, const QString &backupPath, bool allowInvalidCurrent, bool removeStaleLock) +{ + return restoreLibraryImpl(libraryPath, backupPath, allowInvalidCurrent, removeStaleLock, false); +} + +DatabaseRestoreResult DataBaseManagement::restoreLibraryImpl(const QString &libraryPath, const QString &backupPath, bool allowInvalidCurrent, bool removeStaleLock, bool maintenanceLockHeld) { DatabaseRestoreResult result; QString selectedVersion; @@ -1696,13 +1991,16 @@ DatabaseRestoreResult DataBaseManagement::restoreLibrary(const QString &libraryP return result; } - LibraryMaintenanceLock lock(libraryPath); - if (!lock.tryLock(removeStaleLock)) { - result.status = DatabaseRestoreStatus::LockFailed; - result.error = lock.errorString(); - result.lockHolderInfo = lock.holderInfo(); - result.lockHolderIsRunningLocally = lock.holderIsRunningLocally(); - return result; + std::unique_ptr lock; + if (!maintenanceLockHeld) { + lock = std::make_unique(libraryPath); + if (!lock->tryLock(removeStaleLock)) { + result.status = lock->error() == QLockFile::LockFailedError ? DatabaseRestoreStatus::LockFailed : DatabaseRestoreStatus::Failed; + result.error = lock->errorString(); + result.lockHolderInfo = lock->holderInfo(); + result.lockHolderIsRunningLocally = lock->holderIsRunning(); + return result; + } } if (!recoverInterruptedRestore(libraryPath, &result.error, true)) { @@ -1811,8 +2109,10 @@ DatabaseRestoreResult DataBaseManagement::restoreLibrary(const QString &libraryP return result; } -bool DataBaseManagement::recoverInterruptedRestore(const QString &libraryPath, QString *error, bool maintenanceLockHeld) +bool DataBaseManagement::recoverInterruptedRestore(const QString &libraryPath, QString *error, bool maintenanceLockHeld, QLockFile::LockError *lockError) { + if (lockError) + *lockError = QLockFile::NoError; const auto databasePath = LibraryPaths::libraryDatabasePath(libraryPath); const auto stagedPath = stagedDatabasePath(databasePath); const auto rollbackPath = rollbackDatabasePath(databasePath); @@ -1824,6 +2124,8 @@ bool DataBaseManagement::recoverInterruptedRestore(const QString &libraryPath, Q if (!maintenanceLockHeld) { lock = std::make_unique(libraryPath); if (!lock->tryLock()) { + if (lockError) + *lockError = lock->error(); if (error) *error = lock->errorString(); return false; @@ -1876,7 +2178,16 @@ bool DataBaseManagement::recoverInterruptedRestore(const QString &libraryPath, Q bool DataBaseManagement::prepareForRecreation(const QString &libraryPath, QString *error, bool maintenanceLockHeld) { - if (!recoverInterruptedRestore(libraryPath, error, maintenanceLockHeld)) + std::unique_ptr lock; + if (!maintenanceLockHeld) { + lock = std::make_unique(libraryPath); + if (!lock->tryLock()) { + if (error) + *error = lock->errorString(); + return false; + } + } + if (!recoverInterruptedRestore(libraryPath, error, true)) return false; const auto databasePath = LibraryPaths::libraryDatabasePath(libraryPath); @@ -1909,6 +2220,16 @@ bool DataBaseManagement::isLibraryDatabaseValid(const QString &libraryPath) DatabaseSalvageResult DataBaseManagement::salvageLibrary(const QString &libraryPath, bool removeStaleLock) { DatabaseSalvageResult result; + LibraryMaintenanceLock lock(libraryPath); + if (!lock.tryLock(removeStaleLock)) { + result.status = lock.error() == QLockFile::LockFailedError ? DatabaseSalvageStatus::LockFailed : DatabaseSalvageStatus::Failed; + result.error = lock.errorString(); + result.lockHolderInfo = lock.holderInfo(); + result.lockHolderIsRunningLocally = lock.holderIsRunning(); + return result; + } + if (!recoverInterruptedRestore(libraryPath, &result.error, true)) + return result; const auto databasePath = LibraryPaths::libraryDatabasePath(libraryPath); if (!QFile::exists(databasePath)) { result.error = QString("Library database not found: %1").arg(databasePath); @@ -1921,15 +2242,6 @@ DatabaseSalvageResult DataBaseManagement::salvageLibrary(const QString &libraryP const auto salvagePath = salvageDatabasePath(databasePath); { - LibraryMaintenanceLock lock(libraryPath); - if (!lock.tryLock(removeStaleLock)) { - result.status = DatabaseSalvageStatus::LockFailed; - result.error = lock.errorString(); - result.lockHolderInfo = lock.holderInfo(); - result.lockHolderIsRunningLocally = lock.holderIsRunningLocally(); - return result; - } - result.preservedDatabasePath = damagedDatabasePath(libraryPath); if (!copyDatabaseUnit(databasePath, result.preservedDatabasePath, &result.error)) { result.preservedDatabasePath.clear(); @@ -1971,9 +2283,9 @@ DatabaseSalvageResult DataBaseManagement::salvageLibrary(const QString &libraryP } // the rebuilt database is installed through the restore machinery to get its - // staging, rollback, and crash-recovery guarantees; restoreLibrary acquires - // the maintenance lock itself, so it is released above - const auto restoreResult = restoreLibrary(libraryPath, salvagePath, true); + // staging, rollback, and crash-recovery guarantees. Keep the same lock: + // another update must not enter between rebuilding and installing it. + const auto restoreResult = restoreLibraryImpl(libraryPath, salvagePath, true, false, true); QFile::remove(salvagePath); if (!restoreResult.success()) { result.error = restoreResult.error; diff --git a/YACReaderLibrary/db/data_base_management.h b/YACReaderLibrary/db/data_base_management.h index 73db5b780..af8ca8254 100644 --- a/YACReaderLibrary/db/data_base_management.h +++ b/YACReaderLibrary/db/data_base_management.h @@ -57,26 +57,42 @@ enum class DatabaseBackupReason { Manual }; +struct LibraryMaintenanceLockFailure { + QLockFile::LockError error { QLockFile::NoError }; + bool ownerIsRunning { false }; + QString owner; + QString recoveryToken; +}; + class LibraryMaintenanceLock { public: explicit LibraryMaintenanceLock(const QString &libraryPath); ~LibraryMaintenanceLock(); + // true is an explicit recovery request, after confirming no other machine + // is using the library. Reuse this object to detect ownership changes. bool tryLock(bool removeStaleLock = false); + QLockFile::LockError error() const; QString errorString() const; QString holderInfo() const; - bool holderIsRunningLocally() const; + bool holderIsRunning() const; + QString recoveryToken() const; + LibraryMaintenanceLockFailure failure() const; private: bool tryLockFile(QLockFile &lock, bool removeStaleLock); - void captureLockInfo(QLockFile &lock); + QByteArray captureLockInfo(QLockFile &lock); std::unique_ptr maintenanceLock; std::unique_ptr legacyRepairLock; QString failedLockPath; + QLockFile::LockError lastError { QLockFile::NoError }; QString currentHolderInfo; - bool currentHolderIsRunningLocally { false }; + bool currentHolderIsRunning { false }; + QHash observedLocks; + QHash expectedLocks; + QString recoveryError; }; enum class DatabaseRestoreStatus { @@ -131,6 +147,7 @@ class DataBaseManagement : public QObject static bool addColumns(const QString &tableName, const QStringList &columnDefs, const QSqlDatabase &db); static bool addConstraint(const QString &tableName, const QString &constraint, const QSqlDatabase &db); + static DatabaseRestoreResult restoreLibraryImpl(const QString &libraryPath, const QString &backupPath, bool allowInvalidCurrent, bool removeStaleLock, bool maintenanceLockHeld); public: DataBaseManagement(); @@ -144,6 +161,15 @@ class DataBaseManagement : public QObject static bool createComicInfoTable(QSqlDatabase &database, QString tableName); static bool createComicInfoTable9_14(QSqlDatabase &database, QString tableName); static bool createV8Tables(QSqlDatabase &database); + static bool createV10_5Tables(QSqlDatabase &database); + static bool createPlaceholderTable(QSqlDatabase &database); + static bool createReadingListEntryTable(QSqlDatabase &database); + static bool createLabelEntryTable(QSqlDatabase &database); + static bool createComicDeleteTrigger(QSqlDatabase &database); + static bool createSmartListTable(QSqlDatabase &database); + // the folder tables and the folder_id columns of reading_list and smart_list + static bool createListFolderTables(QSqlDatabase &database); + static bool createFolderPlaceholderTable(QSqlDatabase &database); static void exportComicsInfo(QString source, QString dest); static bool importComicsInfo(QString source, QString dest); @@ -153,7 +179,7 @@ class DataBaseManagement : public QObject static bool updateToCurrentVersion(const QString &libraryPath, bool maintenanceLockHeld = false); static bool backupLibrary(const QString &libraryPath, DatabaseBackupReason reason, QString *error = nullptr, const QString &destinationPath = { }, const QString &protectedBackup = { }); static DatabaseRestoreResult restoreLibrary(const QString &libraryPath, const QString &backupPath, bool allowInvalidCurrent = false, bool removeStaleLock = false); - static bool recoverInterruptedRestore(const QString &libraryPath, QString *error = nullptr, bool maintenanceLockHeld = false); + static bool recoverInterruptedRestore(const QString &libraryPath, QString *error = nullptr, bool maintenanceLockHeld = false, QLockFile::LockError *lockError = nullptr); static bool prepareForRecreation(const QString &libraryPath, QString *error = nullptr, bool maintenanceLockHeld = false); static QFileInfoList libraryBackups(const QString &libraryPath); static bool isLibraryDatabaseValid(const QString &libraryPath); diff --git a/YACReaderLibrary/db/folder_item.cpp b/YACReaderLibrary/db/folder_item.cpp index 8b539b4ab..6be7b56e8 100644 --- a/YACReaderLibrary/db/folder_item.cpp +++ b/YACReaderLibrary/db/folder_item.cpp @@ -4,10 +4,9 @@ #include -FolderItem::FolderItem(const QList &data, FolderItem *parent) +FolderItem::FolderItem(const Folder &folder, FolderItem *parent) + : parentItem(parent), folder(folder) { - parentItem = parent; - itemData = data; } FolderItem::~FolderItem() @@ -23,14 +22,14 @@ void FolderItem::appendChild(FolderItem *item) childItems.append(item); else { FolderItem *last = childItems.back(); - QString nameLast = last->data(0).toString(); // TODO usar info name si est� disponible, sino el nombre del fichero..... - QString nameCurrent = item->data(0).toString(); + QString nameLast = last->folder.name; + QString nameCurrent = item->folder.name; QList::iterator i; i = childItems.end(); i--; while (naturalSortLessThanCI(nameCurrent, nameLast) && i != childItems.begin()) { i--; - nameLast = (*i)->data(0).toString(); + nameLast = (*i)->folder.name; } if (!naturalSortLessThanCI(nameCurrent, nameLast)) // si se ha encontrado un elemento menor que current, se inserta justo despu�s childItems.insert(++i, item); @@ -51,21 +50,6 @@ int FolderItem::childCount() const return childItems.count(); } -int FolderItem::columnCount() const -{ - return itemData.count(); -} - -QVariant FolderItem::data(int column) const -{ - return itemData.value(column); -} - -void FolderItem::setData(int column, const QVariant &value) -{ - itemData[column] = value; -} - void FolderItem::addChild(FolderItem *child, int childIndex) { child->parentItem = this; @@ -82,12 +66,6 @@ void FolderItem::removeChild(int childIndex) childItems.removeAt(childIndex); } -void FolderItem::clearChildren() -{ - qDeleteAll(childItems); - childItems.clear(); -} - QList FolderItem::children() { return childItems; @@ -98,11 +76,6 @@ FolderItem *FolderItem::parent() return parentItem; } -void FolderItem::setData(const QList &data) -{ - itemData = data; -} - int FolderItem::row() const { if (parentItem) @@ -110,8 +83,3 @@ int FolderItem::row() const return 0; } - -QList FolderItem::getData() const -{ - return itemData; -} diff --git a/YACReaderLibrary/db/folder_item.h b/YACReaderLibrary/db/folder_item.h index 071e0a9b4..c42ab4f8c 100644 --- a/YACReaderLibrary/db/folder_item.h +++ b/YACReaderLibrary/db/folder_item.h @@ -41,6 +41,8 @@ #ifndef TREEITEM_H #define TREEITEM_H +#include "folder.h" + #include #include #include @@ -48,33 +50,25 @@ class FolderItem { public: - FolderItem(const QList &data, FolderItem *parent = nullptr); + explicit FolderItem(const Folder &folder, FolderItem *parent = nullptr); ~FolderItem(); void appendChild(FolderItem *child); FolderItem *child(int row); int childCount() const; - int columnCount() const; - QVariant data(int column) const; - QList getData() const; int row() const; FolderItem *parent(); FolderItem *parentItem; - unsigned long long int id; - QList comicNames; + Folder folder; FolderItem *originalItem; - void setData(const QList &data); - void setData(int column, const QVariant &value); void addChild(FolderItem *child, int childIndex); void removeChild(FolderItem *child); void removeChild(int childIndex); - void clearChildren(); QList children(); private: QList childItems; - QList itemData; }; //! [0] diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index 8ade93f03..1552290f7 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -5,6 +5,7 @@ #include "folder.h" #include "folder_item.h" #include "qnaturalsorting.h" +#include "table_projection.h" #include "yacreader_global.h" #include @@ -53,77 +54,47 @@ QIcon drawFinishedFolderIcon(const QPixmap &overlay) return finishedIcon; } -struct FolderColumns { - int name; - int path; - int finished; - int completed; - int id; - int parentId; - int numChildren; - int firstChildHash; - int customImage; - int type; - int added; - int updated; - - FolderColumns(QSqlQuery &sqlquery) - { - auto record = sqlquery.record(); - - name = record.indexOf("name"); - path = record.indexOf("path"); - finished = record.indexOf("finished"); - completed = record.indexOf("completed"); - id = record.indexOf("id"); - parentId = record.indexOf("parentId"); - numChildren = record.indexOf("numChildren"); - firstChildHash = record.indexOf("firstChildHash"); - customImage = record.indexOf("customImage"); - type = record.indexOf("type"); - added = record.indexOf("added"); - updated = record.indexOf("updated"); +// The values have the types the views got from the SQL query. +QVariant columnValue(const Folder &folder, int column) +{ + // SQLite gives a NULL as a null QString whatever the column type + const QVariant sqlNull(QMetaType::fromType()); + switch (column) { + case FolderModel::Name: + return folder.name; + case FolderModel::Path: + return folder.path; + case FolderModel::Finished: + return qlonglong(folder.finished ? 1 : 0); + case FolderModel::Completed: + return qlonglong(folder.completed ? 1 : 0); + case FolderModel::NumChildren: + return qlonglong(folder.numChildren); + case FolderModel::FirstChildHash: + return folder.firstChildHash.isNull() ? sqlNull : QVariant(folder.firstChildHash); + case FolderModel::CustomImage: + return folder.customImage.isNull() ? sqlNull : QVariant(folder.customImage); + case FolderModel::Type: + return qlonglong(static_cast(folder.type)); + case FolderModel::Added: + return folder.added != 0 ? QVariant(qlonglong(folder.added)) : sqlNull; + case FolderModel::Updated: + return folder.updated != 0 ? QVariant(qlonglong(folder.updated)) : sqlNull; } -}; - -QList folderDataFromQuery(QSqlQuery &query, const FolderColumns &columns) -{ - QList data; - - data << query.value(columns.name); - data << query.value(columns.path); - data << query.value(columns.finished); - data << query.value(columns.completed); - data << query.value(columns.numChildren); - data << query.value(columns.firstChildHash); - data << query.value(columns.customImage); - data << query.value(columns.type); - data << query.value(columns.added); - data << query.value(columns.updated); - - return data; + return QVariant(); } // TODO: load from DB, type is pretty much needed FolderItem *createRoot(QSqlDatabase &db) { - QList data; - - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT * FROM folder WHERE id = 1"); - selectQuery.exec(); - - auto columns = FolderColumns(selectQuery); - - if (!selectQuery.next()) { + auto rootFolder = DBHelper::loadFolder(FolderModel::RootFolderId, db); + if (!rootFolder) { return nullptr; } - data = folderDataFromQuery(selectQuery, columns); - data[0] = "root"; + rootFolder->name = "root"; - auto root = new FolderItem(data); - root->id = FolderModel::RootFolderId; + auto root = new FolderItem(*rootFolder); root->parentItem = nullptr; return root; @@ -157,7 +128,7 @@ FolderModel::~FolderModel() int FolderModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) - return static_cast(parent.internalPointer())->columnCount(); + return FolderModel::Updated + 1; else { if (rootItem == nullptr) { return 0; @@ -221,13 +192,13 @@ void FolderModel::takeUpdatedChildrenInfo(FolderItem *parent, const QModelIndex auto updatedChild = updatedChildren[j]; // same folder - auto sameFolderId = child->id == updatedChild->id; // and also same name + auto sameFolderId = child->folder.id.value() == updatedChild->folder.id.value(); // and also same name if (sameFolderId) { // 1. check if child data needs to be udpated - if (child->getData() != updatedChild->getData()) { + if (child->folder != updatedChild->folder) { auto modelIndexToUpdate = index(m, 0, parentModelIndex); - child->setData(updatedChild->getData()); + child->folder = updatedChild->folder; emit dataChanged(modelIndexToUpdate, modelIndexToUpdate); } @@ -241,8 +212,8 @@ void FolderModel::takeUpdatedChildrenInfo(FolderItem *parent, const QModelIndex continue; } - auto childName = child->data(Name).toString(); - auto childUpdatedName = updatedChild->data(Name).toString(); + auto childName = child->folder.name; + auto childUpdatedName = updatedChild->folder.name; // folder added if (!naturalSortLessThanCI(childName, childUpdatedName)) { @@ -299,31 +270,7 @@ void FolderModel::takeUpdatedChildrenInfo(FolderItem *parent, const QModelIndex Folder FolderModel::folderFromItem(FolderItem *folderItem) const { - auto name = folderItem->data(FolderModel::Name).toString(); - auto parentItem = folderItem->parent(); - - QString path; - if (parentItem == nullptr) { - parentItem = rootItem; - path = "/"; - } else { - path = parentItem->data(Columns::Path).toString() + "/" + name; - } - - auto folder = Folder(folderItem->id, - parentItem->id, - name, - path, - folderItem->data(Columns::Completed).toBool(), - folderItem->data(Columns::Finished).toBool(), - folderItem->data(Columns::NumChildren).toInt(), - folderItem->data(Columns::FirstChildHash).toString(), - folderItem->data(Columns::CustomImage).toString(), - folderItem->data(Columns::Type).value(), - folderItem->data(Columns::Added).toLongLong(), - folderItem->data(Columns::Updated).toLongLong()); - - return folder; + return folderItem->folder; } void FolderModel::reload(const QModelIndex &index) @@ -340,8 +287,8 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const auto item = static_cast(index.internalPointer()); if (role == Qt::ToolTipRole) { - QString toolTip = item->data(FolderModel::Name).toString(); - int totalNumOfChildren = item->childCount() + item->comicNames.size(); + QString toolTip = item->folder.name; + int totalNumOfChildren = item->childCount(); if (totalNumOfChildren > 0) { toolTip = toolTip + " - " + QString::number(totalNumOfChildren); } @@ -350,43 +297,43 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const } if (role == Qt::DecorationRole) { - if (item->data(FolderModel::Finished).toBool()) + if (item->folder.finished) return QVariant(folderFinishedIcon); else return QVariant(folderIcon); } if (role == FolderModel::FolderNameRole) { - return item->data(FolderModel::Name); + return columnValue(item->folder, FolderModel::Name); } if (role == FolderModel::CompletedRole) - return item->data(FolderModel::Completed); + return columnValue(item->folder, FolderModel::Completed); if (role == FolderModel::FinishedRole) - return item->data(FolderModel::Finished); + return columnValue(item->folder, FolderModel::Finished); if (role == FolderModel::IdRole) - return item->id; + return item->folder.id.value(); if (role == FolderModel::CoverPathRole) { - if (item->data(FolderModel::CustomImage).toString().isEmpty()) - return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString()); + if (item->folder.customImage.isEmpty()) + return getCoverUrlPathForComicHash(item->folder.firstChildHash); else - return getCoverUrlPathForFolderId(item->id); + return getCoverUrlPathForFolderId(item->folder.id.value()); } if (role == FolderModel::NumChildrenRole) - return item->data(NumChildren); + return columnValue(item->folder, NumChildren); if (role == FolderModel::TypeRole) - return item->data(Type); + return columnValue(item->folder, Type); if (role == FolderModel::AddedRole) - return item->data(Added); + return columnValue(item->folder, Added); if (role == FolderModel::UpdatedRole) - return item->data(Updated); + return columnValue(item->folder, Updated); if (role == FolderModel::ShowRecentRole) return showRecent; @@ -397,7 +344,7 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const if (role != Qt::DisplayRole) return QVariant(); - return item->data(index.column()); + return columnValue(item->folder, index.column()); } Qt::ItemFlags FolderModel::flags(const QModelIndex &index) const @@ -417,7 +364,7 @@ QVariant FolderModel::headerData(int section, } if (orientation == Qt::Horizontal && role == Qt::DisplayRole) - return rootItem->data(section); + return columnValue(rootItem->folder, section); return QVariant(); } @@ -451,7 +398,7 @@ QModelIndex FolderModel::index(qulonglong folderId) const } auto item = static_cast(idx.internalPointer()); - if (item->id == folderId) { + if (item->folder.id.value() == folderId) { index = idx; return false; } @@ -522,7 +469,7 @@ FolderModel::ModelData FolderModel::createModelData(const QString &path) const QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(path); - QSqlQuery selectQuery("select * from folder where id <> 1 order by parentId,name", db); + QSqlQuery selectQuery("SELECT " + TableProjection::selectList("f", Folder::columns()) + " FROM folder f WHERE f.id <> 1 ORDER BY f.parentId, f.name", db); auto root = createRoot(db); modelData = createModelData(selectQuery, root); @@ -539,19 +486,17 @@ FolderModel::ModelData FolderModel::createModelData(QSqlQuery &sqlquery, FolderI QMap itemsLookup; // add parent to the lookup - itemsLookup.insert(parent->id, parent); - - auto columns = FolderColumns(sqlquery); + itemsLookup.insert(parent->folder.id.value(), parent); + const auto indexes = Folder::columnIndexes(sqlquery.record(), "f"); while (sqlquery.next()) { - auto item = new FolderItem(folderDataFromQuery(sqlquery, columns)); + auto item = new FolderItem(Folder::fromQuery(sqlquery, indexes)); - item->id = sqlquery.value(columns.id).toULongLong(); // la inserci�n de hijos se hace de forma ordenada - FolderItem *parent = itemsLookup.value(sqlquery.value(columns.parentId).toULongLong()); + FolderItem *parent = itemsLookup.value(item->folder.parentId.value()); parent->appendChild(item); // se a�ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones - itemsLookup.insert(item->id, item); + itemsLookup.insert(item->folder.id.value(), item); } return FolderModel::ModelData { parent, itemsLookup }; @@ -566,7 +511,7 @@ QString FolderModel::getFolderPath(const QModelIndex &folder) { if (!folder.isValid()) // root folder return "/"; - return static_cast(folder.internalPointer())->data(FolderModel::Path).toString(); + return static_cast(folder.internalPointer())->folder.path; } void FolderModel::updateFolderCompletedStatus(const QModelIndexList &list, bool status) @@ -580,11 +525,12 @@ void FolderModel::updateFolderCompletedStatus(const QModelIndexList &list, bool db.transaction(); for (const auto &mi : list) { auto item = static_cast(mi.internalPointer()); - item->setData(FolderModel::Completed, status); + item->folder.completed = status; - Folder f = DBHelper::loadFolder(item->id, db); - f.completed = status; - DBHelper::update(f, db); + if (auto f = DBHelper::loadFolder(item->folder.id.value(), db)) { + f->completed = status; + DBHelper::update(*f, db); + } } db.commit(); connectionName = db.connectionName(); @@ -606,11 +552,12 @@ void FolderModel::updateFolderFinishedStatus(const QModelIndexList &list, bool s db.transaction(); for (const auto &mi : list) { auto item = static_cast(mi.internalPointer()); - item->setData(FolderModel::Finished, status); + item->folder.finished = status; - Folder f = DBHelper::loadFolder(item->id, db); - f.finished = status; - DBHelper::update(f, db); + if (auto f = DBHelper::loadFolder(item->folder.id.value(), db)) { + f->finished = status; + DBHelper::update(*f, db); + } } db.commit(); connectionName = db.connectionName(); @@ -635,7 +582,7 @@ void FolderModel::updateFolderType(const QModelIndexList &list, YACReader::FileT std::function setType; setType = [&setType](FolderItem *item, YACReader::FileType type) -> void { - item->setData(FolderModel::Type, QVariant::fromValue(type)); + item->folder.type = type; const auto children = item->children(); for (auto child : children) { @@ -645,7 +592,7 @@ void FolderModel::updateFolderType(const QModelIndexList &list, YACReader::FileT setType(item, type); - DBHelper::updateFolderTreeType(item->id, db, type); + DBHelper::updateFolderTreeType(item->folder.id.value(), db, type); } db.commit(); connectionName = db.connectionName(); @@ -662,8 +609,8 @@ bool FolderModel::renameFolder(const QModelIndex &folder, const QString &name, Q return false; auto item = static_cast(folder.internalPointer()); - const auto oldPath = item->data(FolderModel::Path).toString(); - const auto parentPath = item->parent()->data(FolderModel::Path).toString(); + const auto oldPath = item->folder.path; + const auto parentPath = item->parent()->folder.path; const auto newPath = QDir::cleanPath(parentPath + "/" + name); QString connectionName; @@ -678,7 +625,7 @@ bool FolderModel::renameFolder(const QModelIndex &folder, const QString &name, Q } else if (!db.transaction()) { if (error != nullptr) *error = db.lastError().text(); - } else if (!DBHelper::renameFolder(item->id, name, oldPath, newPath, db, error)) { + } else if (!DBHelper::renameFolder(item->folder.id.value(), name, oldPath, newPath, db, error)) { db.rollback(); } else if (!db.commit()) { if (error != nullptr) @@ -693,11 +640,11 @@ bool FolderModel::renameFolder(const QModelIndex &folder, const QString &name, Q if (!success) return false; - item->setData(FolderModel::Name, name); + item->folder.name = name; const auto updatePath = [&oldPath, &newPath](auto &&self, FolderItem *folderItem) -> void { - const auto path = folderItem->data(FolderModel::Path).toString(); - folderItem->setData(FolderModel::Path, newPath + path.mid(oldPath.size())); + const auto path = folderItem->folder.path; + folderItem->folder.path = newPath + path.mid(oldPath.size()); const auto children = folderItem->children(); for (auto child : children) self(self, child); @@ -709,7 +656,7 @@ bool FolderModel::renameFolder(const QModelIndex &folder, const QString &name, Q auto newRow = 0; const auto siblings = parentItem->children(); for (auto sibling : siblings) { - if (sibling != item && !naturalSortLessThanCI(name, sibling->data(FolderModel::Name).toString())) + if (sibling != item && !naturalSortLessThanCI(name, sibling->folder.name)) ++newRow; } @@ -737,7 +684,7 @@ void FolderModel::updateTreeType(YACReader::FileType type) std::function setType; setType = [&setType](FolderItem *item, YACReader::FileType type) -> void { - item->setData(FolderModel::Type, QVariant::fromValue(type)); + item->folder.type = type; const auto children = item->children(); for (auto child : children) { @@ -762,11 +709,12 @@ void FolderModel::setCustomFolderCover(const QModelIndex &index, const QString & db.transaction(); auto item = static_cast(index.internalPointer()); - item->setData(FolderModel::CustomImage, path); + item->folder.customImage = path; - Folder f = DBHelper::loadFolder(item->id, db); - f.customImage = path; - DBHelper::update(f, db); + if (auto f = DBHelper::loadFolder(item->folder.id.value(), db)) { + f->customImage = path; + DBHelper::update(*f, db); + } db.commit(); connectionName = db.connectionName(); @@ -785,11 +733,12 @@ void FolderModel::resetFolderCover(const QModelIndex &index) db.transaction(); auto item = static_cast(index.internalPointer()); - item->setData(FolderModel::CustomImage, ""); + item->folder.customImage = ""; - Folder f = DBHelper::loadFolder(item->id, db); - f.customImage = ""; - DBHelper::update(f, db); + if (auto f = DBHelper::loadFolder(item->folder.id.value(), db)) { + f->customImage = ""; + DBHelper::update(*f, db); + } db.commit(); connectionName = db.connectionName(); @@ -799,30 +748,6 @@ void FolderModel::resetFolderCover(const QModelIndex &index) emit dataChanged(index, index); } -QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi) -{ - QStringList result; - qulonglong id = 1; - if (mi.isValid()) { - auto item = static_cast(mi.internalPointer()); - id = item->id; - } - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - db.transaction(); - - result = DBHelper::loadSubfoldersNames(id, db); - - db.commit(); - connectionName = db.connectionName(); - } - QSqlDatabase::removeDatabase(connectionName); - - std::sort(result.begin(), result.end(), naturalSortLessThanCI); - return result; -} - Folder FolderModel::getRootFolder() const { if (this->rootItem == nullptr) { @@ -855,7 +780,7 @@ QModelIndex FolderModel::getIndexFromFolderId(qulonglong folderId, const QModelI if (modelIndex.isValid()) { auto folderItem = static_cast(modelIndex.internalPointer()); - if (folderItem->id == folderId) { + if (folderItem->folder.id.value() == folderId) { return modelIndex; } @@ -864,7 +789,7 @@ QModelIndex FolderModel::getIndexFromFolderId(qulonglong folderId, const QModelI if (childModelIndex.isValid()) { auto folderItem = static_cast(childModelIndex.internalPointer()); - if (folderItem->id == folderId) { + if (folderItem->folder.id.value() == folderId) { return childModelIndex; } } @@ -876,7 +801,7 @@ QModelIndex FolderModel::getIndexFromFolderId(qulonglong folderId, const QModelI QModelIndex FolderModel::getIndexFromFolder(const Folder &folder, const QModelIndex &parent) { - return getIndexFromFolderId(folder.id, parent); + return getIndexFromFolderId(folder.id.value(), parent); } QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QModelIndex &parent) @@ -890,41 +815,28 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod Folder newFolder; newFolder.name = folderName; - newFolder.parentId = parentItem->id; - newFolder.path = parentItem->data(Columns::Path).toString() + "/" + folderName; - newFolder.type = parentItem->data(Columns::Type).value(); + newFolder.parentId = FolderId(parentItem->folder.id.value()); + newFolder.path = parentItem->folder.path + "/" + folderName; + newFolder.type = parentItem->folder.type; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - newFolder.id = DBHelper::insert(&newFolder, db); - DBHelper::updateChildrenInfo(parentItem->id, db); + newFolder.id = FolderId(DBHelper::insert(&newFolder, db)); + DBHelper::updateChildrenInfo(parentItem->folder.id.value(), db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); int destRow = 0; - QList data; - data << newFolder.name; - data << newFolder.path; - data << false; // finished - data << true; // completed - data << 0; // numChildren - data << QVariant(); // first child hash, new folder is empty - data << QVariant(); // custom cover - data << QVariant::fromValue(newFolder.type); - data << newFolder.added; - data << newFolder.updated; - - auto item = new FolderItem(data); - item->id = newFolder.id; + auto item = new FolderItem(newFolder); beginInsertRows(parent, 0, 0); // TODO calculate the destRow before inserting the new child parentItem->appendChild(item); destRow = parentItem->children().indexOf(item); // TODO optimize this, appendChild should return the index of the new item - items.insert(item->id, item); + items.insert(item->folder.id.value(), item); endInsertRows(); @@ -988,17 +900,25 @@ void FolderModel::deleteFolder(const QModelIndex &mi) parent->removeChild(mi.row()); Folder f; - f.setId(item->id); + f.id = item->folder.id; + QStringList placeholderCoverKeys; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + db.transaction(); DBHelper::removeFromDB(&f, db); - auto folder = DBHelper::updateChildrenInfo(item->parent()->id, db); + placeholderCoverKeys = DBHelper::removeUnusedPlaceholders(db).value_or(QStringList()); + auto folder = DBHelper::updateChildrenInfo(item->parent()->folder.id.value(), db); DBHelper::propagateFolderUpdatesToParent(folder, db); + if (!db.commit()) { + db.rollback(); + placeholderCoverKeys.clear(); + } connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + DBHelper::removePlaceholderCovers(_databasePath, placeholderCoverKeys); endRemoveRows(); } @@ -1038,7 +958,7 @@ bool FolderModelProxy::filterAcceptsRow(int source_row, const QModelIndex &sourc FolderItem *item = parent->children().at(source_row); - return filteredItems.contains(item->id); + return filteredItems.contains(item->folder.id.value()); } void FolderModelProxy::setFilterData(QMap *filteredItems, FolderItem *root) diff --git a/YACReaderLibrary/db/folder_model.h b/YACReaderLibrary/db/folder_model.h index 28c4b31a1..fe1b7100a 100644 --- a/YACReaderLibrary/db/folder_model.h +++ b/YACReaderLibrary/db/folder_model.h @@ -78,8 +78,6 @@ class FolderModel : public QAbstractItemModel, protected Themable void setCustomFolderCover(const QModelIndex &index, const QString &path); void resetFolderCover(const QModelIndex &index); - QStringList getSubfoldersNames(const QModelIndex &mi); - Folder getRootFolder() const; Folder getFolder(const QModelIndex &mi) const; QModelIndex getIndexFromFolderId(qulonglong folderId, const QModelIndex &parent = QModelIndex()); diff --git a/YACReaderLibrary/db/folder_query_result_processor.cpp b/YACReaderLibrary/db/folder_query_result_processor.cpp index 751aca9dd..fa4ad5667 100644 --- a/YACReaderLibrary/db/folder_query_result_processor.cpp +++ b/YACReaderLibrary/db/folder_query_result_processor.cpp @@ -1,6 +1,7 @@ #include "folder_query_result_processor.h" #include "data_base_management.h" +#include "db_helper.h" #include "folder_item.h" #include "folder_model.h" #include "search_query.h" @@ -46,10 +47,10 @@ void YACReader::FolderQueryResultProcessor::setupFilteredModelData(QSqlQuery &sq FolderItem *rootItem = 0; // inicializar el nodo ra�z - QList rootData; - rootData << "root"; - rootItem = new FolderItem(rootData); - rootItem->id = ROOT; + Folder rootFolder; + rootFolder.id = FolderId(ROOT); + rootFolder.name = "root"; + rootItem = new FolderItem(rootFolder); rootItem->parentItem = 0; FolderItem *parent = rootItem; @@ -57,25 +58,22 @@ void YACReader::FolderQueryResultProcessor::setupFilteredModelData(QSqlQuery &sq QMap *filteredItems = new QMap(); // add tree root node - filteredItems->insert(parent->id, parent); - - QSqlRecord record = sqlquery.record(); - - int parentIdIndex = record.indexOf("parentId"); + filteredItems->insert(parent->folder.id.value(), parent); + const auto indexes = Folder::columnIndexes(sqlquery.record(), "f"); while (sqlquery.next()) { - auto item = new FolderItem(QList()); // no need for data, we just need the ids of the folders in the search result - item->id = sqlquery.value(0).toULongLong(); + const auto folder = Folder::fromQuery(sqlquery, indexes); + auto item = new FolderItem(folder); // id del padre - quint64 parentId = sqlquery.value(parentIdIndex).toULongLong(); + quint64 parentId = folder.parentId.value(); // se a�ade el item al map, de forma que se pueda encontrar como padre en siguientes iteraciones - if (!filteredItems->contains(item->id)) - filteredItems->insert(item->id, item); + if (!filteredItems->contains(item->folder.id.value())) + filteredItems->insert(item->folder.id.value(), item); // es necesario conocer las coordenadas de origen para poder realizar scroll autom�tico en la vista - item->originalItem = model->items.value(item->id); + item->originalItem = model->items.value(item->folder.id.value()); // si el padre ya existe en el modelo, el item se a�ade como hijo if (filteredItems->contains(parentId)) @@ -90,8 +88,7 @@ void YACReader::FolderQueryResultProcessor::setupFilteredModelData(QSqlQuery &sq // el padre no estaba en el modelo filtrado, as� que se rescata del modelo original FolderItem *parentItem = model->items.value(parentId); // se debe crear un nuevo nodo (para no compartir los hijos con el nodo original) - FolderItem *newparentItem = new FolderItem(parentItem->getData()); // padre que se a�adir� a la estructura de directorios filtrados - newparentItem->id = parentId; + FolderItem *newparentItem = new FolderItem(parentItem->folder); // padre que se a�adir� a la estructura de directorios filtrados newparentItem->originalItem = parentItem; @@ -103,13 +100,13 @@ void YACReader::FolderQueryResultProcessor::setupFilteredModelData(QSqlQuery &sq // sino se registra el nodo para poder encontrarlo con posterioridad y se a�ade el item actual como hijo else { newparentItem->appendChild(item); - filteredItems->insert(newparentItem->id, newparentItem); + filteredItems->insert(newparentItem->folder.id.value(), newparentItem); parentPreviousInserted = false; } // variables de control del bucle, se avanza hacia el nodo padre item = newparentItem; - parentId = parentItem->parentItem->id; + parentId = parentItem->parentItem->folder.id.value(); } // si el nodo es hijo de 1 y no hab�a sido previamente insertado como hijo, se a�ade como tal diff --git a/YACReaderLibrary/db/list_entry.cpp b/YACReaderLibrary/db/list_entry.cpp new file mode 100644 index 000000000..cea2d2557 --- /dev/null +++ b/YACReaderLibrary/db/list_entry.cpp @@ -0,0 +1,43 @@ +#include "list_entry.h" + +#include "table_projection.h" + +#include +#include + +const QStringList &ListEntry::columns(ListKind listKind) +{ + static const QStringList readingListColumns = { "reading_list_id", "ordering", "last_known_name", "last_known_path", "source", "source_format" }; + static const QStringList labelColumns = { "label_id", "ordering", "last_known_name", "last_known_path" }; + + switch (listKind) { + case ListKind::ReadingList: + return readingListColumns; + case ListKind::Label: + return labelColumns; + } + return readingListColumns; +} + +QList ListEntry::columnIndexes(const QSqlRecord &record, const QString &alias, ListKind listKind) +{ + return TableProjection::columnIndexes(record, alias, columns(listKind)); +} + +ListEntry ListEntry::fromQuery(const QSqlQuery &query, const QList &columnIndexes, ListKind listKind) +{ + const auto value = [&](int column) { return query.value(columnIndexes.at(column)); }; + + ListEntry entry; + entry.id = { listKind, value(0).toULongLong() }; + entry.listId = value(1).toULongLong(); + entry.ordering = value(2).toInt(); + const auto name = value(3); + const auto path = value(4); + if (!name.isNull() || !path.isNull()) { + entry.lastKnownFile = LastKnownFile { name.toString(), path.toString() }; + } + if (listKind == ListKind::ReadingList && !value(5).isNull()) + entry.source = EntrySource { value(5).toString(), value(6).toString() }; + return entry; +} diff --git a/YACReaderLibrary/db/list_entry.h b/YACReaderLibrary/db/list_entry.h new file mode 100644 index 000000000..b3432a6a0 --- /dev/null +++ b/YACReaderLibrary/db/list_entry.h @@ -0,0 +1,46 @@ +#ifndef LIST_ENTRY_H +#define LIST_ENTRY_H + +#include "library_ids.h" + +#include +#include + +#include + +class QSqlQuery; +class QSqlRecord; + +struct LastKnownFile { + QString name; + QString path; + + friend bool operator==(const LastKnownFile &, const LastKnownFile &) = default; +}; + +// the CBL book of an imported reading list entry, as in the file +struct EntrySource { + QString text; + // "xml" or "json" + QString format; + + friend bool operator==(const EntrySource &, const EntrySource &) = default; +}; + +// a row of reading_list_entry or label_entry; what the entry refers to is the case of the row that holds it +struct ListEntry { + ListItemId id { }; + qulonglong listId = 0; + int ordering = 0; + std::optional lastKnownFile; + // only reading list entries have one + std::optional source; + + friend bool operator==(const ListEntry &, const ListEntry &) = default; + + static const QStringList &columns(ListKind listKind); + static QList columnIndexes(const QSqlRecord &record, const QString &alias, ListKind listKind); + static ListEntry fromQuery(const QSqlQuery &query, const QList &columnIndexes, ListKind listKind); +}; + +#endif diff --git a/YACReaderLibrary/db/list_folder.cpp b/YACReaderLibrary/db/list_folder.cpp new file mode 100644 index 000000000..bcf40dfe4 --- /dev/null +++ b/YACReaderLibrary/db/list_folder.cpp @@ -0,0 +1,30 @@ +#include "list_folder.h" + +#include "table_projection.h" + +#include +#include + +const QStringList &ListFolder::columns() +{ + static const QStringList columns = { "parent_id", "name", "ordering" }; + return columns; +} + +QList ListFolder::columnIndexes(const QSqlRecord &record, const QString &alias) +{ + return TableProjection::columnIndexes(record, alias, columns()); +} + +// the indexes are of {id} + columns() +ListFolder ListFolder::fromQuery(const QSqlQuery &query, const QList &columnIndexes, ListFolderKind kind) +{ + ListFolder folder; + folder.id = { kind, query.value(columnIndexes.at(0)).toULongLong() }; + const auto parentId = query.value(columnIndexes.at(1)); + if (!parentId.isNull()) + folder.parentId = parentId.toULongLong(); + folder.name = query.value(columnIndexes.at(2)).toString(); + folder.ordering = query.value(columnIndexes.at(3)).toInt(); + return folder; +} diff --git a/YACReaderLibrary/db/list_folder.h b/YACReaderLibrary/db/list_folder.h new file mode 100644 index 000000000..b02101084 --- /dev/null +++ b/YACReaderLibrary/db/list_folder.h @@ -0,0 +1,38 @@ +#ifndef LIST_FOLDER_H +#define LIST_FOLDER_H + +#include "library_ids.h" +#include "qnaturalsorting.h" + +#include +#include + +#include +#include + +class QSqlQuery; +class QSqlRecord; + +// a folder of the reading lists or of the smart lists section; it holds lists of its kind and folders of its kind +struct ListFolder { + ListFolderId id { }; + // a folder of the same kind; nothing at the top level + std::optional parentId; + QString name; + int ordering = 0; + + static const QStringList &columns(); + static QList columnIndexes(const QSqlRecord &record, const QString &alias); + static ListFolder fromQuery(const QSqlQuery &query, const QList &columnIndexes, ListFolderKind kind); +}; + +// the order of the folders, and of the lists, in a folder of a lists section, from their load order; the insert by +// name puts an item before the ones with an equal name, so the last one loaded comes first +template +void sortListSectionItems(QList &items, Name name) +{ + std::reverse(items.begin(), items.end()); + std::stable_sort(items.begin(), items.end(), [&name](const Item &a, const Item &b) { return naturalSortLessThanCI(name(a), name(b)); }); +} + +#endif diff --git a/YACReaderLibrary/db/placeholder.cpp b/YACReaderLibrary/db/placeholder.cpp new file mode 100644 index 000000000..63fd4f163 --- /dev/null +++ b/YACReaderLibrary/db/placeholder.cpp @@ -0,0 +1,58 @@ +#include "placeholder.h" + +#include "table_projection.h" + +#include +#include + +const QStringList &Placeholder::columns() +{ + static const QStringList columns = [] { + QStringList result; + for (const auto &column : DescriptiveComicMetadata::columns()) + result.append(QString::fromLatin1(column.name)); + result.append("coverKey"); + return result; + }(); + return columns; +} + +QList Placeholder::columnIndexes(const QSqlRecord &record, const QString &alias) +{ + return TableProjection::columnIndexes(record, alias, columns()); +} + +// `columnIndexes` are of {id} + Placeholder::columns() +Placeholder Placeholder::fromQuery(const QSqlQuery &query, const QList &columnIndexes) +{ + const auto value = [&](int column) { return query.value(columnIndexes.at(column)); }; + + Placeholder placeholder; + placeholder.id = PlaceholderId(value(0).toULongLong()); + + int column = 1; + for (const auto &descriptive : DescriptiveComicMetadata::columns()) { + placeholder.*descriptive.member = value(column++); + } + + const auto coverKey = value(column); + if (!coverKey.isNull()) { + placeholder.coverKey = coverKey.toString(); + } + return placeholder; +} + +bool Placeholder::hasSameValues(const Placeholder &other) const +{ + for (const auto &column : DescriptiveComicMetadata::columns()) { + if (this->*column.member != other.*column.member) + return false; + } + return id == other.id && coverKey == other.coverKey; +} + +void Placeholder::bindTo(QSqlQuery &query) const +{ + DescriptiveComicMetadata::bindTo(query); + query.bindValue(":coverKey", coverKey ? QVariant(*coverKey) : QVariant()); +} diff --git a/YACReaderLibrary/db/placeholder.h b/YACReaderLibrary/db/placeholder.h new file mode 100644 index 000000000..81d694ad6 --- /dev/null +++ b/YACReaderLibrary/db/placeholder.h @@ -0,0 +1,28 @@ +#ifndef PLACEHOLDER_H +#define PLACEHOLDER_H + +#include "descriptive_comic_metadata.h" +#include "library_ids.h" + +#include +#include + +#include + +class QSqlQuery; +class QSqlRecord; + +// an expected issue without a file; it has no file, reading, or reader data +struct Placeholder : DescriptiveComicMetadata { + PlaceholderId id; + std::optional coverKey; + + static const QStringList &columns(); + static QList columnIndexes(const QSqlRecord &record, const QString &alias); + static Placeholder fromQuery(const QSqlQuery &query, const QList &columnIndexes); + void bindTo(QSqlQuery &query) const; + // compares every column + bool hasSameValues(const Placeholder &other) const; +}; + +#endif diff --git a/YACReaderLibrary/db/query_parser.h b/YACReaderLibrary/db/query_parser.h index 20b4e0874..ab7b8e804 100644 --- a/YACReaderLibrary/db/query_parser.h +++ b/YACReaderLibrary/db/query_parser.h @@ -11,8 +11,8 @@ #include #include -#define SEARCH_FOLDERS_QUERY "SELECT DISTINCT f.* FROM folder f LEFT JOIN comic c ON (f.id = c.parentId) INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE " -#define SEARCH_COMICS_QUERY "SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.date,ci.added,ci.type,ci.lastTimeOpened,ci.series,ci.volume,ci.storyArc FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) LEFT JOIN folder f ON (f.id == c.parentId) WHERE " +#define SEARCH_FOLDERS_FROM "FROM folder f LEFT JOIN comic c ON (f.id = c.parentId) INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE " +#define SEARCH_COMICS_FROM "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) LEFT JOIN folder f ON (f.id == c.parentId) WHERE " /** * This class is used to generate an SQL query string from a search expression, diff --git a/YACReaderLibrary/db/reading_list.cpp b/YACReaderLibrary/db/reading_list.cpp index 4b9d0279d..b50a3327b 100644 --- a/YACReaderLibrary/db/reading_list.cpp +++ b/YACReaderLibrary/db/reading_list.cpp @@ -1,41 +1,104 @@ #include "reading_list.h" -ReadingList::ReadingList(const QString &name, qulonglong id, int ordering) - : name(name), id(id), ordering(ordering) +#include "table_projection.h" + +#include +#include + +const QStringList &ReadingList::columns() +{ + static const QStringList columns = { "parentId", "ordering", "name", "finished", "completed", "folder_id" }; + return columns; +} + +namespace { + +// `value(k)` is column k of {id} + ReadingList::columns() +template +ReadingList readReadingList(const Value &value) +{ + ReadingList list; + list.id = ReadingListId(value(0).toULongLong()); + const auto parentId = value(1); + if (!parentId.isNull()) { + list.parentId = ReadingListId(parentId.toULongLong()); + } + list.ordering = value(2).toInt(); + list.name = value(3).toString(); + list.finished = value(4).toBool(); + list.completed = value(5).toBool(); + const auto folderId = value(6); + if (!folderId.isNull()) { + list.folderId = folderId.toULongLong(); + } + return list; +} + +// `value(k)` is column k of {id} + Label::columns() +template +Label readLabel(const Value &value) +{ + Label label; + label.id = LabelId(value(0).toULongLong()); + label.name = value(1).toString(); + label.color = value(2).toString(); + label.ordering = value(3).toInt(); + return label; +} + +} + +ReadingList ReadingList::fromRecord(const QSqlRecord &record, const QString &alias) +{ + const auto indexes = columnIndexes(record, alias); + return readReadingList([&](int column) { return record.value(indexes.at(column)); }); +} + +QList ReadingList::columnIndexes(const QSqlRecord &record, const QString &alias) { + return TableProjection::columnIndexes(record, alias, columns()); } -qulonglong ReadingList::getId() const +ReadingList ReadingList::fromQuery(const QSqlQuery &query, const QList &columnIndexes) { - return id; + return readReadingList([&](int column) { return query.value(columnIndexes.at(column)); }); } -QString ReadingList::getName() const +void ReadingList::bindTo(QSqlQuery &query) const { - return name; + query.bindValue(":parentId", parentId ? QVariant(parentId->value()) : QVariant()); + query.bindValue(":ordering", ordering); + query.bindValue(":name", name); + query.bindValue(":finished", finished); + query.bindValue(":completed", completed); + query.bindValue(":folder_id", folderId ? QVariant(*folderId) : QVariant()); } -int ReadingList::getOrdering() const +const QStringList &Label::columns() { - return ordering; + static const QStringList columns = { "name", "color", "ordering" }; + return columns; } -Label::Label(const QString &name, qulonglong id, YACReader::LabelColors colorid) - : name(name), id(id), colorid(colorid) +Label Label::fromRecord(const QSqlRecord &record, const QString &alias) { + const auto indexes = columnIndexes(record, alias); + return readLabel([&](int column) { return record.value(indexes.at(column)); }); } -YACReader::LabelColors Label::getColorID() const +QList Label::columnIndexes(const QSqlRecord &record, const QString &alias) { - return colorid; + return TableProjection::columnIndexes(record, alias, columns()); } -QString Label::getName() const +Label Label::fromQuery(const QSqlQuery &query, const QList &columnIndexes) { - return name; + return readLabel([&](int column) { return query.value(columnIndexes.at(column)); }); } -qulonglong Label::getId() const +void Label::bindTo(QSqlQuery &query) const { - return id; + query.bindValue(":name", name); + query.bindValue(":color", color); + query.bindValue(":ordering", ordering); } diff --git a/YACReaderLibrary/db/reading_list.h b/YACReaderLibrary/db/reading_list.h index d60e8e173..d9be15877 100644 --- a/YACReaderLibrary/db/reading_list.h +++ b/YACReaderLibrary/db/reading_list.h @@ -1,36 +1,44 @@ #ifndef READING_LIST_H #define READING_LIST_H -#include "yacreader_global.h" +#include "library_ids.h" -class ReadingList -{ -public: - ReadingList(const QString &name, qulonglong id, int ordering); +#include +#include - qulonglong getId() const; - QString getName() const; - int getOrdering() const; +#include -private: +class QSqlQuery; +class QSqlRecord; + +struct ReadingList { + ReadingListId id; + std::optional parentId; + int ordering = 0; QString name; - qulonglong id; - int ordering; + bool finished = false; + bool completed = true; + // a reading list folder; only top level lists (no parentId) can be in one + std::optional folderId; + + static const QStringList &columns(); + static ReadingList fromRecord(const QSqlRecord &record, const QString &alias); + static QList columnIndexes(const QSqlRecord &record, const QString &alias); + static ReadingList fromQuery(const QSqlQuery &query, const QList &columnIndexes); + void bindTo(QSqlQuery &query) const; }; -class Label -{ -public: - Label(const QString &name, qulonglong id, YACReader::LabelColors colorid); - - YACReader::LabelColors getColorID() const; - QString getName() const; - qulonglong getId() const; - -private: +struct Label { + LabelId id; QString name; - qulonglong id; - YACReader::LabelColors colorid; + QString color; + int ordering = 0; + + static const QStringList &columns(); + static Label fromRecord(const QSqlRecord &record, const QString &alias); + static QList columnIndexes(const QSqlRecord &record, const QString &alias); + static Label fromQuery(const QSqlQuery &query, const QList &columnIndexes); + void bindTo(QSqlQuery &query) const; }; #endif // READING_LIST_H diff --git a/YACReaderLibrary/db/reading_list_item.cpp b/YACReaderLibrary/db/reading_list_item.cpp index 985fe4aad..782c564ac 100644 --- a/YACReaderLibrary/db/reading_list_item.cpp +++ b/YACReaderLibrary/db/reading_list_item.cpp @@ -6,20 +6,7 @@ #include -ListItem::ListItem(const QList &data) - : itemData(data) -{ -} - -int ListItem::columnCount() -{ - return itemData.count(); -} - -QVariant ListItem::data(int column) const -{ - return itemData.at(column); -} +#include qulonglong ListItem::getId() const { @@ -28,26 +15,23 @@ qulonglong ListItem::getId() const //------------------------------------------------------ -SpecialListItem::SpecialListItem(const QList &data) - : ListItem(data) +SpecialListItem::SpecialListItem(const SpecialList &list) + : list(list) { } QIcon SpecialListItem::getIcon() const { - if (itemData.count() > Id) { - int id = itemData.at(Id).toInt(); - const auto &icons = ThemeManager::instance().getCurrentTheme().readingListIcons; - switch (id) { - case 0: - return icons.readingListIcon; - case 1: - return icons.favoritesIcon; - case 2: - return icons.currentlyReadingIcon; - default: - break; - } + const auto &icons = ThemeManager::instance().getCurrentTheme().readingListIcons; + switch (list.id) { + case 0: + return icons.readingListIcon; + case 1: + return icons.favoritesIcon; + case 2: + return icons.currentlyReadingIcon; + default: + break; } QLOG_WARN() << "Icon for SpecialListItem not available"; @@ -55,42 +39,34 @@ QIcon SpecialListItem::getIcon() const return QIcon(); } -qulonglong SpecialListItem::getId() const +QString SpecialListItem::name() const { - if (itemData.count() > Id) { - return itemData.at(Id).toInt(); - } + return list.name; +} - return 0; +qulonglong SpecialListItem::getId() const +{ + return list.id; } ReadingListModel::TypeSpecialList SpecialListItem::getType() const { - if (itemData.count() > Id) { - int id = itemData.at(Id).toInt(); - return (ReadingListModel::TypeSpecialList)id; - } - - QLOG_WARN() << "TypeSpecialList not available"; - - return (ReadingListModel::TypeSpecialList)0; + return (ReadingListModel::TypeSpecialList)list.id; } //------------------------------------------------------ -LabelItem::LabelItem(const QList &data) - : ListItem(data) +LabelItem::LabelItem(const Label &label) + : label(label) { } QIcon LabelItem::getIcon() const { - if (itemData.count() > Color) { - QString color = itemData.at(Color).toString().toLower(); - const auto &icons = ThemeManager::instance().getCurrentTheme().readingListIcons; - if (icons.labelIcons.contains(color)) { - return icons.labelIcons[color]; - } + const QString color = label.color.toLower(); + const auto &icons = ThemeManager::instance().getCurrentTheme().readingListIcons; + if (icons.labelIcons.contains(color)) { + return icons.labelIcons[color]; } QLOG_WARN() << "Icon for label item not available"; @@ -100,60 +76,35 @@ QIcon LabelItem::getIcon() const YACReader::LabelColors LabelItem::colorid() const { - if (itemData.count() > Ordering) { - return YACReader::LabelColors(itemData.at(Ordering).toInt()); - } - - QLOG_WARN() << "Label color for label item not available"; - - return (YACReader::LabelColors)0; + return YACReader::LabelColors(label.ordering); } QString LabelItem::name() const { - if (itemData.count() > Name) { - return itemData.at(Name).toString(); - } - - QLOG_WARN() << "Name for label item not available"; - - return ""; + return label.name; } void LabelItem::setName(const QString &name) { - if (itemData.count() > Name) { - itemData[Name] = name; - } + label.name = name; } qulonglong LabelItem::getId() const { - if (itemData.count() > Id) { - return YACReader::LabelColors(itemData.at(Id).toULongLong()); - } - - QLOG_WARN() << "Id for Label item not available"; - - return 0; + return label.id.value(); } //------------------------------------------------------ -ReadingListItem::ReadingListItem(const QList &data, ReadingListItem *p) - : ListItem(data), parent(p) +ReadingListItem::ReadingListItem(const ReadingList &list, ReadingListItem *p) + : parent(p), list(list) { } +// a child list is a list too; folders of lists have their own icon QIcon ReadingListItem::getIcon() const { - const auto &theme = ThemeManager::instance().getCurrentTheme(); - if (parent->getId() == 0) - return theme.readingListIcons.listIcon; // top level list - else if (theme.sidebarIcons.useSystemFolderIcons) - return QFileIconProvider().icon(QFileIconProvider::Folder); - else - return theme.navigationTree.folderIcon; // sublist + return ThemeManager::instance().getCurrentTheme().readingListIcons.listIcon; } int ReadingListItem::childCount() const @@ -217,43 +168,27 @@ void ReadingListItem::removeChild(ReadingListItem *item) qulonglong ReadingListItem::getId() const { - if (itemData.count() > Id) - return itemData.at(Id).toULongLong(); - - QLOG_WARN() << "Name for reading list item not available"; - - return 0; + return list.id.value(); } QString ReadingListItem::name() const { - if (itemData.count() > Name) - return itemData.at(Name).toString(); - - QLOG_WARN() << "Name for reading list item not available"; - - return ""; + return list.name; } void ReadingListItem::setName(const QString &name) { - if (itemData.count() > Name) - itemData[Name] = name; + list.name = name; } int ReadingListItem::getOrdering() const { - if (itemData.count() > Ordering) - return itemData[Ordering].toInt(); - - QLOG_WARN() << "Ordering for Item not available"; - return 0; + return list.ordering; } void ReadingListItem::setOrdering(const int ordering) { - if (itemData.count() > Ordering) - itemData[Ordering] = ordering; + list.ordering = ordering; } QList ReadingListItem::children() @@ -269,12 +204,146 @@ int ReadingListItem::row() const return 0; } -ReadingListSeparatorItem::ReadingListSeparatorItem() - : ListItem(QList()) +SmartListItem::SmartListItem(const ::SmartList &list) + : list(list) +{ +} + +QIcon SmartListItem::getIcon() const +{ + return ThemeManager::instance().getCurrentTheme().readingListIcons.smartListIcon; +} + +QString SmartListItem::name() const +{ + return list.name; +} + +qulonglong SmartListItem::getId() const +{ + return list.id.value(); +} + +//------------------------------------------------------ + +ListFolderItem::ListFolderItem(const ListFolder &folder) + : folder(folder) +{ +} + +ListFolderItem::~ListFolderItem() +{ + qDeleteAll(folders); + qDeleteAll(lists); +} + +// the icon of the folders of the library tree +QIcon ListFolderItem::getIcon() const +{ + const auto &theme = ThemeManager::instance().getCurrentTheme(); + if (theme.sidebarIcons.useSystemFolderIcons) + return QFileIconProvider().icon(QFileIconProvider::Folder); + return theme.navigationTree.folderIcon; +} + +QString ListFolderItem::name() const +{ + return folder.name; +} + +qulonglong ListFolderItem::getId() const +{ + return folder.id.id; +} + +int ListFolderItem::childCount() const +{ + return folders.count() + lists.count(); +} + +ListItem *ListFolderItem::child(int row) const +{ + if (row < folders.count()) + return folders.at(row); + return lists.at(row - folders.count()); +} + +int ListFolderItem::rowOf(const ListItem *child) const +{ + for (int i = 0; i < folders.count(); ++i) { + if (folders.at(i) == child) + return i; + } + const auto list = lists.indexOf(const_cast(child)); + return list < 0 ? -1 : folders.count() + list; +} + +int ListFolderItem::insertFolderByName(ListFolderItem *item) +{ + item->container = this; + const auto row = rowForFolderName(item->name()); + folders.insert(row, item); + return row; +} + +int ListFolderItem::insertListByName(ListItem *item) +{ + const auto row = rowForListName(item->name()); + lists.insert(row - folders.count(), item); + return row; +} + +void ListFolderItem::appendFolder(ListFolderItem *item) +{ + item->container = this; + folders.append(item); +} + +void ListFolderItem::appendList(ListItem *item) +{ + lists.append(item); +} + +void ListFolderItem::sortByName() +{ + const auto name = [](const ListItem *item) { return item->name(); }; + sortListSectionItems(folders, name); + sortListSectionItems(lists, name); +} + +int ListFolderItem::rowForFolderName(const QString &name, const ListItem *ignored) const { + return std::count_if(folders.cbegin(), folders.cend(), [&](const ListFolderItem *folder) { + return folder != ignored && naturalSortLessThanCI(folder->name(), name); + }); } +int ListFolderItem::rowForListName(const QString &name, const ListItem *ignored) const +{ + return folders.count() + std::count_if(lists.cbegin(), lists.cend(), [&](const ListItem *list) { + return list != ignored && naturalSortLessThanCI(list->name(), name); + }); +} + +void ListFolderItem::removeChild(ListItem *item) +{ + for (int i = 0; i < folders.count(); ++i) { + if (folders.at(i) == item) { + folders.removeAt(i); + return; + } + } + lists.removeOne(item); +} + +//------------------------------------------------------ + QIcon ReadingListSeparatorItem::getIcon() const { return QIcon(); } + +QString ReadingListSeparatorItem::name() const +{ + return QString(); +} diff --git a/YACReaderLibrary/db/reading_list_item.h b/YACReaderLibrary/db/reading_list_item.h index 61bfbf6e5..f6b364267 100644 --- a/YACReaderLibrary/db/reading_list_item.h +++ b/YACReaderLibrary/db/reading_list_item.h @@ -1,70 +1,64 @@ #ifndef READING_LIST_ITEM_H #define READING_LIST_ITEM_H +#include "list_folder.h" +#include "reading_list.h" #include "reading_list_model.h" +#include "smart_list.h" #include -#include -// TODO add propper constructors, using QList is not safe +#include + +// Favorites (default_reading_list), Reading, and Recent: not rows of a table type +struct SpecialList { + qulonglong id; + QString name; +}; class ListItem { public: - ListItem(const QList &data); - int columnCount(); virtual QIcon getIcon() const = 0; - QVariant data(int column) const; + virtual QString name() const = 0; virtual qulonglong getId() const; - QList itemData; virtual ~ListItem() { } }; -//------------------------------------------------------ - class SpecialListItem : public ListItem { public: - SpecialListItem(const QList &data); + explicit SpecialListItem(const SpecialList &list); QIcon getIcon() const override; + QString name() const override; qulonglong getId() const override; ReadingListModel::TypeSpecialList getType() const; -private: - enum DataIndexes { - Name, - Id - }; + SpecialList list; }; -//------------------------------------------------------ - class LabelItem : public ListItem { public: - LabelItem(const QList &data); + explicit LabelItem(const Label &label); QIcon getIcon() const override; YACReader::LabelColors colorid() const; - QString name() const; + QString name() const override; void setName(const QString &name); qulonglong getId() const override; -private: - enum DataIndexes { - Name, - Color, - Id, - Ordering - }; + Label label; }; -//------------------------------------------------------ +class ListFolderItem; class ReadingListItem : public ListItem { public: - ReadingListItem(const QList &data, ReadingListItem *parent = nullptr); + explicit ReadingListItem(const ReadingList &list, ReadingListItem *parent = nullptr); QIcon getIcon() const override; ReadingListItem *parent; + // the folder (or the root of the reading lists section) that holds a top level list; nothing for a child list + ListFolderItem *container = nullptr; int childCount() const; int row() const; ReadingListItem *child(int row); @@ -72,31 +66,71 @@ class ReadingListItem : public ListItem void appendChild(ReadingListItem *item, int pos); void removeChild(ReadingListItem *item); qulonglong getId() const override; - QString name() const; + QString name() const override; void setName(const QString &name); int getOrdering() const; void setOrdering(const int ordering); QList children(); + ReadingList list; + private: QList childItems; +}; + +class SmartListItem : public ListItem +{ +public: + explicit SmartListItem(const ::SmartList &list); + QIcon getIcon() const override; + QString name() const override; + qulonglong getId() const override; - enum DataIndexes { - Name, - Id, - Finished, - Completed, - Ordering - }; + ::SmartList list; + // the folder, or the root of the smart lists section + ListFolderItem *container = nullptr; }; -//------------------------------------------------------ +// a folder of lists, or the root of a section (no id, not a row); its children are its folders, then its lists. It +// deletes what it holds. +class ListFolderItem : public ListItem +{ +public: + explicit ListFolderItem(const ListFolder &folder); + ~ListFolderItem() override; + QIcon getIcon() const override; + QString name() const override; + qulonglong getId() const override; + + int childCount() const; + ListItem *child(int row) const; + // -1 when it is not a child + int rowOf(const ListItem *child) const; + // among its folders, or among its lists, by name; gives its row + int insertFolderByName(ListFolderItem *item); + int insertListByName(ListItem *item); + // a load adds every child at the end and sorts once: the order of adding them one by one by name + void appendFolder(ListFolderItem *item); + void appendList(ListItem *item); + void sortByName(); + // the row a folder or a list with that name gets, with `ignored` left out + int rowForFolderName(const QString &name, const ListItem *ignored = nullptr) const; + int rowForListName(const QString &name, const ListItem *ignored = nullptr) const; + void removeChild(ListItem *item); + + ListFolder folder; + // the folder that holds it; nothing for the root of a section + ListFolderItem *container = nullptr; + QList folders; + // ReadingListItem or SmartListItem, of the kind of the folder + QList lists; +}; class ReadingListSeparatorItem : public ListItem { public: - ReadingListSeparatorItem(); QIcon getIcon() const override; + QString name() const override; }; #endif // READING_LIST_ITEM_H diff --git a/YACReaderLibrary/db/reading_list_model.cpp b/YACReaderLibrary/db/reading_list_model.cpp index 85b5f7297..35daebc7c 100644 --- a/YACReaderLibrary/db/reading_list_model.cpp +++ b/YACReaderLibrary/db/reading_list_model.cpp @@ -3,25 +3,37 @@ #include "QsLog.h" #include "data_base_management.h" #include "db_helper.h" +#include "list_folder.h" #include "qnaturalsorting.h" #include "reading_list_item.h" +#include "smart_list.h" #include "yacreader_global_gui.h" #include +#include + ReadingListModel::ReadingListModel(QObject *parent) : QAbstractItemModel(parent), rootItem(0) { separator1 = new ReadingListSeparatorItem; separator2 = new ReadingListSeparatorItem; + separator3 = new ReadingListSeparatorItem; +} + +ReadingListModel::~ReadingListModel() +{ + cleanAll(); + delete separator1; + delete separator2; + delete separator3; } int ReadingListModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) // TOP { - int separatorsCount = 2; // labels.isEmpty()?1:2; - return specialLists.count() + labels.count() + rootItem->childCount() + separatorsCount; + return readingListsRoot == nullptr ? 0 : readingListsStart() + readingListsRoot->childCount(); } else { auto item = static_cast(parent.internalPointer()); @@ -29,6 +41,9 @@ int ReadingListModel::rowCount(const QModelIndex &parent) const auto item = static_cast(parent.internalPointer()); return item->childCount(); } + + if (typeid(*item) == typeid(ListFolderItem)) + return static_cast(item)->childCount(); } return 0; @@ -65,6 +80,12 @@ QVariant ReadingListModel::data(const QModelIndex &index, int role) const if (typeid(*item) == typeid(ReadingListItem)) return QVariant(ReadingListModel::ReadingList); + if (typeid(*item) == typeid(SmartListItem)) + return QVariant(ReadingListModel::SmartList); + + if (typeid(*item) == typeid(ListFolderItem)) + return QVariant(static_cast(item)->folder.id.kind == ListFolderKind::ReadingList ? ReadingListModel::ReadingListFolder : ReadingListModel::SmartListFolder); + if (typeid(*item) == typeid(ReadingListSeparatorItem)) return QVariant(ReadingListModel::Separator); } @@ -94,20 +115,29 @@ QVariant ReadingListModel::data(const QModelIndex &index, int role) const if (role != Qt::DisplayRole) return QVariant(); - return item->data(index.column()); + return index.column() == 0 ? QVariant(item->name()) : QVariant(); } Qt::ItemFlags ReadingListModel::flags(const QModelIndex &index) const { + // the view takes a dragged row between top level rows and on empty space only when the root takes drops if (!index.isValid()) - return { }; + return Qt::ItemIsDropEnabled; auto item = static_cast(index.internalPointer()); if (typeid(*item) == typeid(ReadingListSeparatorItem)) return { }; - if (typeid(*item) == typeid(ReadingListItem) && static_cast(item)->parent->getId() != 0) - return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; // only sublists are dragable + if (typeid(*item) == typeid(ReadingListItem)) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; + + // the comics of a smart list come from its search + if (typeid(*item) == typeid(SmartListItem)) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; + + // a folder takes folders and lists, not comics + if (typeid(*item) == typeid(ListFolderItem)) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; } @@ -115,7 +145,7 @@ Qt::ItemFlags ReadingListModel::flags(const QModelIndex &index) const QVariant ReadingListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) - return rootItem->data(section); + return section == 0 ? QVariant(rootItem->name()) : QVariant(); return QVariant(); } @@ -126,8 +156,6 @@ QModelIndex ReadingListModel::index(int row, int column, const QModelIndex &pare return QModelIndex(); if (!parent.isValid()) { - int separatorsCount = 2; // labels.isEmpty()?1:2; - if (rowIsSpecialList(row, parent)) return createIndex(row, column, specialLists.at(row)); @@ -135,97 +163,79 @@ QModelIndex ReadingListModel::index(int row, int column, const QModelIndex &pare return createIndex(row, column, separator1); if (rowIsLabel(row, parent)) - return createIndex(row, column, labels.at(row - specialLists.count() - 1)); + return createIndex(row, column, labels.at(row - labelsStart())); - if (separatorsCount == 2) - if (row == specialLists.count() + labels.count() + 1) - return createIndex(row, column, separator2); + if (row == labelsStart() + labels.count()) + return createIndex(row, column, separator2); - if (rowIsReadingList(row, parent)) - return createIndex(row, column, rootItem->child(row - (specialLists.count() + labels.count() + separatorsCount))); + if (row >= smartListsStart() && row < smartListsStart() + smartListsRoot->childCount()) + return createIndex(row, column, smartListsRoot->child(row - smartListsStart())); - } else // sublist - { - ReadingListItem *parentItem; + if (smartListsRoot->childCount() > 0 && row == smartListsStart() + smartListsRoot->childCount()) + return createIndex(row, column, separator3); - if (!parent.isValid()) - parentItem = rootItem; // this should be impossible - else - parentItem = static_cast(parent.internalPointer()); + if (row >= readingListsStart()) + return createIndex(row, column, readingListsRoot->child(row - readingListsStart())); - ReadingListItem *childItem = parentItem->child(row); - return createIndex(row, column, childItem); + return QModelIndex(); } - /*FolderItem *childItem = parentItem->child(row); - if (childItem) - return createIndex(row, column, childItem); - else*/ - return QModelIndex(); + + auto parentItem = static_cast(parent.internalPointer()); + if (typeid(*parentItem) == typeid(ListFolderItem)) + return createIndex(row, column, static_cast(parentItem)->child(row)); + + // sublist + return createIndex(row, column, static_cast(parentItem)->child(row)); } QModelIndex ReadingListModel::parent(const QModelIndex &index) const { - if (!index.isValid()) return QModelIndex(); auto item = static_cast(index.internalPointer()); if (typeid(*item) == typeid(ReadingListItem)) { - auto childItem = static_cast(index.internalPointer()); - ReadingListItem *parent = childItem->parent; - if (parent->getId() != 0) - return createIndex(parent->row() + specialLists.count() + labels.count() + 2, 0, parent); + auto childItem = static_cast(item); + if (childItem->parent != rootItem) + return indexOfItem(childItem->parent); } + if (auto *container = containerOf(item); container != nullptr && container->container != nullptr) + return indexOfItem(container); + return QModelIndex(); } bool ReadingListModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { Q_UNUSED(action); + Q_UNUSED(column); QLOG_DEBUG() << "trying to drop into row = " << row << "column column = " << column << "parent" << parent; - if (row == -1) - return false; - - if (!parent.isValid()) // top level items - { - if (row == -1) // no list - return false; - - if (row == 1) // reading is just a smart list - return false; - - if (row == 2) // recent is just a smart list - return false; - - if (rowIsSeparator(row, parent)) + if (data->formats().contains(YACReader::YACReaderLibraryListRowMimeDataFormat)) { + auto *dragged = draggedItem(data); + if (dragged == nullptr) return false; + if (isChildList(dragged)) + return childListDropTarget(static_cast(dragged), row, parent) != nullptr; + return dropContainer(dragged, row, parent) != nullptr; } - if (data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat)) - return true; - - if (rowIsReadingList(row, parent)) // TODO avoid droping in a different parent - { - if (!parent.isValid()) - return false; - else { - QList> sublistsRows; - QByteArray rawData = data->data(YACReader::YACReaderLibrarSubReadingListMimeDataFormat); - QDataStream in(&rawData, QIODevice::ReadOnly); - in >> sublistsRows; // deserialize the list of indentifiers - - if (sublistsRows.isEmpty()) - return false; + if (row == -1) + return false; - if (parent.row() != sublistsRows.at(0).second) - return false; + const auto target = index(row, 0, parent); + if (!target.isValid()) + return false; + auto item = static_cast(target.internalPointer()); - return data->formats().contains(YACReader::YACReaderLibrarSubReadingListMimeDataFormat); - } + if (data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat)) { + // Reading and Recent are filled by the library + if (typeid(*item) == typeid(SpecialListItem)) + return static_cast(item)->getType() == TypeSpecialList::Favorites; + return typeid(*item) == typeid(LabelItem) || typeid(*item) == typeid(ReadingListItem); } return false; @@ -237,8 +247,17 @@ bool ReadingListModel::dropMimeData(const QMimeData *data, Qt::DropAction action if (data->formats().contains(YACReader::YACReaderLibrarComiscSelectionMimeDataFormat)) return dropComics(data, action, row, column, parent); - if (data->formats().contains(YACReader::YACReaderLibrarSubReadingListMimeDataFormat)) - return dropSublist(data, action, row, column, parent); + if (data->formats().contains(YACReader::YACReaderLibraryListRowMimeDataFormat)) { + auto *dragged = draggedItem(data); + if (dragged == nullptr) + return false; + if (isChildList(dragged)) { + auto *destination = childListDropTarget(static_cast(dragged), row, parent); + return destination != nullptr && dropSublist(static_cast(dragged), destination); + } + auto *to = dropContainer(dragged, row, parent); + return to != nullptr && moveItem(dragged, to); + } return false; } @@ -251,104 +270,58 @@ bool ReadingListModel::dropComics(const QMimeData *data, Qt::DropAction action, QLOG_DEBUG() << "dropped : " << comicIds; - QModelIndex dest; - QModelIndex parentDest; - - if (row == -1) { - dest = parent; - } else - dest = index(row, column, parent); - - parentDest = dest.parent(); + const auto dest = row == -1 ? parent : index(row, column, parent); + if (!dest.isValid()) + return false; + auto item = static_cast(dest.internalPointer()); - if (rowIsSpecialList(dest.row(), parentDest)) { - if (dest.row() == 0) // add to favorites - { - QLOG_DEBUG() << "-------addComicsToFavorites : " << comicIds << " to " << dest.data(IDRole).toULongLong(); - emit addComicsToFavorites(comicIds); - return true; - } + if (typeid(*item) == typeid(SpecialListItem) && static_cast(item)->getType() == TypeSpecialList::Favorites) { + QLOG_DEBUG() << "-------addComicsToFavorites : " << comicIds << " to " << dest.data(IDRole).toULongLong(); + emit addComicsToFavorites(comicIds); + return true; } - if (rowIsLabel(dest.row(), parentDest)) { - QLOG_DEBUG() << "+++++++++++addComicsToLabel : " << comicIds << " to " << dest.data(IDRole).toULongLong(); - emit addComicsToLabel(comicIds, dest.data(IDRole).toULongLong()); + if (typeid(*item) == typeid(LabelItem)) { + emit addComicsToLabel(YACReader::mimeDataToRows(data), dest.data(IDRole).toULongLong()); return true; } - if (rowIsReadingList(dest.row(), parentDest)) { - QLOG_DEBUG() << "///////////addComicsToReadingList : " << comicIds << " to " << dest.data(IDRole).toULongLong(); - emit addComicsToReadingList(comicIds, dest.data(IDRole).toULongLong()); + if (typeid(*item) == typeid(ReadingListItem)) { + emit addComicsToReadingList(YACReader::mimeDataToRows(data), dest.data(IDRole).toULongLong()); return true; } return false; } -bool ReadingListModel::dropSublist(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +bool ReadingListModel::dropSublist(ReadingListItem *item, ReadingListItem *destination) { - Q_UNUSED(action); - Q_UNUSED(column); - - QList> sublistsRows; - QByteArray rawData = data->data(YACReader::YACReaderLibrarSubReadingListMimeDataFormat); - QDataStream in(&rawData, QIODevice::ReadOnly); - in >> sublistsRows; // deserialize the list of indentifiers - - QLOG_DEBUG() << "dropped : " << sublistsRows; - - int sourceRow = sublistsRows.at(0).first; - int destRow = row; - QModelIndex destParent = parent; - if (row == -1) { - QLOG_DEBUG() << "droping inside parent"; - destRow = parent.row(); - destParent = parent.parent(); - } - QLOG_DEBUG() << "move " << sourceRow << "-" << destRow; - - if (sourceRow == destRow) - return false; - - // beginMoveRows(destParent,sourceRow,sourceRow,destParent,destRow); - - auto parentItem = static_cast(destParent.internalPointer()); - ReadingListItem *child = parentItem->child(sourceRow); - parentItem->removeChild(child); - parentItem->appendChild(child, destRow); + auto *parentItem = item->parent; + const auto parentIndex = indexOfItem(parentItem); + const auto sourceRow = item->row(); + const auto destinationRow = destination->row(); + beginMoveRows(parentIndex, sourceRow, sourceRow, parentIndex, destinationRow > sourceRow ? destinationRow + 1 : destinationRow); + parentItem->removeChild(item); + parentItem->appendChild(item, destinationRow); + endMoveRows(); reorderingChildren(parentItem->children()); - // endMoveRows(); - return true; } QMimeData *ReadingListModel::mimeData(const QModelIndexList &indexes) const { - QLOG_DEBUG() << "mimeData requested" << indexes; - - if (indexes.length() == 0) { - QLOG_ERROR() << "mimeData requested: indexes is empty"; - return new QMimeData(); // TODO what happens if 0 is returned? - } - - if (indexes.length() > 1) { - QLOG_DEBUG() << "mimeData requested for more than one index, this shouldn't be possible"; - } - - QModelIndex modelIndex = indexes.at(0); - - QList> rows; - rows << QPair(modelIndex.row(), modelIndex.parent().row()); - QLOG_DEBUG() << "mimeData requested for row : " << modelIndex.row(); + if (indexes.isEmpty()) + return nullptr; + // one row at a time: the view selects one row + const auto &mi = indexes.constFirst(); QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); - out << rows; // serialize the list of identifiers + out << mi.data(TypeListsRole).toInt() << mi.data(IDRole).toULongLong(); auto mimeData = new QMimeData(); - mimeData->setData(YACReader::YACReaderLibrarSubReadingListMimeDataFormat, data); - + mimeData->setData(YACReader::YACReaderLibraryListRowMimeDataFormat, data); return mimeData; } @@ -373,6 +346,10 @@ void ReadingListModel::setupReadingListsData(QString path) // separator-------------------------------------------- + setupSmartLists(db); + + // separator, when there are smart lists---------------- + // setup reading list setupReadingLists(db); @@ -390,7 +367,7 @@ void ReadingListModel::addNewLabel(const QString &name, YACReader::LabelColors c QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); qulonglong id = DBHelper::insertLabel(name, color, db); - int newPos = addLabelIntoList(new LabelItem(QList() << name << YACReader::colorToName(color) << id << color)); + int newPos = addLabelIntoList(new LabelItem(::Label { LabelId(id), name, YACReader::colorToName(color), int(color) })); beginInsertRows(QModelIndex(), specialLists.count() + 1 + newPos + 1, specialLists.count() + 1 + newPos + 1); endInsertRows(); @@ -399,28 +376,40 @@ void ReadingListModel::addNewLabel(const QString &name, YACReader::LabelColors c QSqlDatabase::removeDatabase(connectionName); } -void ReadingListModel::addReadingList(const QString &name) +void ReadingListModel::addReadingList(const QString &name, const QModelIndex &folder) { + auto *container = containerFor(ListFolderKind::ReadingList, folder); + std::optional id; QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - beginInsertRows(QModelIndex(), 0, 0); // TODO calculate the right coordinates before inserting - - qulonglong id = DBHelper::insertReadingList(name, db); - ReadingListItem *newItem; - rootItem->appendChild(newItem = new ReadingListItem(QList() - << name - << id - << false - << true - << 0)); - - items.insert(id, newItem); - - endInsertRows(); + id = ReadingListId(DBHelper::insertReadingList(name, db)); + if (container != readingListsRoot && !DBHelper::moveReadingListToFolder(*id, container->getId(), db)) + container = readingListsRoot; connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); + + ::ReadingList list; + list.id = *id; + list.name = name; + if (container != readingListsRoot) + list.folderId = container->getId(); + auto newItem = new ReadingListItem(list, rootItem); + newItem->container = container; + insertChild(container, container->rowForListName(name), [container, newItem] { container->insertListByName(newItem); }); + items.insert(id->value(), newItem); +} + +void ReadingListModel::appendReadingList(ReadingListId id, const QString &name) +{ + ::ReadingList list; + list.id = id; + list.name = name; + auto newItem = new ReadingListItem(list, rootItem); + newItem->container = readingListsRoot; + insertChild(readingListsRoot, readingListsRoot->rowForListName(name), [this, newItem] { readingListsRoot->insertListByName(newItem); }); + items.insert(id.value(), newItem); } void ReadingListModel::addReadingListAt(const QString &name, const QModelIndex &mi) @@ -429,18 +418,19 @@ void ReadingListModel::addReadingListAt(const QString &name, const QModelIndex & { QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - beginInsertRows(mi, 0, 0); // TODO calculate the right coordinates before inserting - auto readingListParent = static_cast(mi.internalPointer()); + // a new child list goes at the end of its parent + beginInsertRows(mi, readingListParent->childCount(), readingListParent->childCount()); + qulonglong id = DBHelper::insertReadingSubList(name, mi.data(IDRole).toULongLong(), readingListParent->childCount(), db); ReadingListItem *newItem; - readingListParent->appendChild(newItem = new ReadingListItem(QList() - << name - << id - << false - << true - << readingListParent->childCount())); + ::ReadingList list; + list.id = ReadingListId(id); + list.parentId = ReadingListId(readingListParent->getId()); + list.name = name; + list.ordering = readingListParent->childCount(); + readingListParent->appendChild(newItem = new ReadingListItem(list)); items.insert(id, newItem); endInsertRows(); @@ -454,7 +444,118 @@ bool ReadingListModel::isEditable(const QModelIndex &mi) if (!mi.isValid()) return false; auto item = static_cast(mi.internalPointer()); - return typeid(*item) != typeid(SpecialListItem); + return typeid(*item) == typeid(LabelItem) || typeid(*item) == typeid(ReadingListItem) || typeid(*item) == typeid(SmartListItem) || + typeid(*item) == typeid(ListFolderItem); +} + +QModelIndex ReadingListModel::addSmartList(const QString &name, const QString &query, const QModelIndex &folder) +{ + auto *container = containerFor(ListFolderKind::SmartList, folder); + std::optional id; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + id = DBHelper::insertSmartList(name, query, db); + if (id && container != smartListsRoot && !DBHelper::moveSmartListToFolder(*id, container->getId(), db)) + container = smartListsRoot; + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (!id) + return { }; + + ::SmartList list; + list.id = *id; + list.name = name; + list.query = query; + if (container != smartListsRoot) + list.folderId = container->getId(); + auto newItem = new SmartListItem(list); + newItem->container = container; + insertChild(container, container->rowForListName(name), [container, newItem] { container->insertListByName(newItem); }); + return indexOfItem(newItem); +} + +QModelIndex ReadingListModel::addListFolder(ListFolderKind kind, const QModelIndex &parentFolder, const QString &name) +{ + auto *container = containerFor(kind, parentFolder); + const auto root = kind == ListFolderKind::ReadingList ? readingListsRoot : smartListsRoot; + std::optional id; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + id = DBHelper::insertListFolder(kind, container == root ? std::nullopt : std::optional(container->getId()), name, db); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (!id) + return { }; + + ListFolder folder; + folder.id = *id; + if (container != root) + folder.parentId = container->getId(); + folder.name = name; + auto newItem = new ListFolderItem(folder); + insertChild(container, container->rowForFolderName(name), [container, newItem] { container->insertFolderByName(newItem); }); + return indexOfItem(newItem); +} + +std::optional ReadingListModel::listFolder(const QModelIndex &mi) const +{ + if (!mi.isValid()) + return std::nullopt; + auto item = static_cast(mi.internalPointer()); + if (typeid(*item) != typeid(ListFolderItem)) + return std::nullopt; + return static_cast(item)->folder.id; +} + +std::optional ReadingListModel::section(const QModelIndex &mi) const +{ + if (!mi.isValid()) + return std::nullopt; + auto item = static_cast(mi.internalPointer()); + if (typeid(*item) == typeid(ReadingListItem)) + return ListFolderKind::ReadingList; + if (typeid(*item) == typeid(SmartListItem)) + return ListFolderKind::SmartList; + if (typeid(*item) == typeid(ListFolderItem)) + return static_cast(item)->folder.id.kind; + return std::nullopt; +} + +std::optional<::SmartList> ReadingListModel::smartList(const QModelIndex &mi) const +{ + if (!mi.isValid()) + return std::nullopt; + auto item = static_cast(mi.internalPointer()); + if (typeid(*item) != typeid(SmartListItem)) + return std::nullopt; + return static_cast(item)->list; +} + +bool ReadingListModel::updateSmartList(const QModelIndex &mi, const QString &name, const QString &query) +{ + if (!smartList(mi)) + return false; + + auto item = static_cast(mi.internalPointer()); + bool updated = false; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + updated = DBHelper::updateSmartList(item->list.id, name, query, db); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (!updated) + return false; + + item->list.name = name; + item->list.query = query; + moveChild(item, item->container); + return true; } bool ReadingListModel::isReadingList(const QModelIndex &mi) @@ -500,16 +601,22 @@ void ReadingListModel::rename(const QModelIndex &mi, const QString &name) rli->setName(name); DBHelper::renameList(item->getId(), name, db); - if (rli->parent->getId() != 0) { - // TODO - // move row depending on the name - } else - emit dataChanged(index(mi.row(), 0), index(mi.row(), 0)); + // child lists keep their order: it is the reading order of their parent + if (isChildList(rli)) + emit dataChanged(mi, mi); + else + moveChild(rli, rli->container); } else if (typeid(*item) == typeid(LabelItem)) { auto li = static_cast(item); li->setName(name); DBHelper::renameLabel(item->getId(), name, db); emit dataChanged(index(mi.row(), 0), index(mi.row(), 0)); + } else if (typeid(*item) == typeid(ListFolderItem)) { + auto folder = static_cast(item); + if (DBHelper::renameListFolder(folder->folder.id, name, db)) { + folder->folder.name = name; + moveChild(folder, folder->container); + } } connectionName = db.connectionName(); } @@ -518,36 +625,55 @@ void ReadingListModel::rename(const QModelIndex &mi, const QString &name) void ReadingListModel::deleteItem(const QModelIndex &mi) { - if (isEditable(mi)) { - QLOG_DEBUG() << "parent row :" << mi.parent().data() << "-" << mi.row(); - beginRemoveRows(mi.parent(), mi.row(), mi.row()); - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); - - auto item = static_cast(mi.internalPointer()); - - if (typeid(*item) == typeid(ReadingListItem)) { - auto rli = static_cast(item); - QLOG_DEBUG() << "num children : " << rli->parent->childCount(); - rli->parent->removeChild(rli); - QLOG_DEBUG() << "num children : " << rli->parent->childCount(); - DBHelper::removeListFromDB(item->getId(), db); - if (rli->parent->getId() != 0) { - reorderingChildren(rli->parent->children()); - } - QLOG_DEBUG() << "num children : " << rli->parent->childCount(); - } else if (typeid(*item) == typeid(LabelItem)) { - auto li = static_cast(item); - labels.removeOne(li); - DBHelper::removeLabelFromDB(item->getId(), db); - } - connectionName = db.connectionName(); + if (!isEditable(mi)) + return; + + auto item = static_cast(mi.internalPointer()); + std::optional placeholderCovers; + bool removed = false; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + if (typeid(*item) == typeid(SmartListItem)) { + removed = DBHelper::removeSmartList(static_cast(item)->list.id, db); + } else if (typeid(*item) == typeid(ListFolderItem)) { + placeholderCovers = DBHelper::removeListFolder(static_cast(item)->folder.id, db); + removed = placeholderCovers.has_value(); + } else if (typeid(*item) == typeid(ReadingListItem)) { + placeholderCovers = DBHelper::removeListFromDB(item->getId(), db); + removed = placeholderCovers.has_value(); + } else if (typeid(*item) == typeid(LabelItem)) { + placeholderCovers = DBHelper::removeLabelFromDB(item->getId(), db); + removed = placeholderCovers.has_value(); } - QSqlDatabase::removeDatabase(connectionName); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (placeholderCovers) + DBHelper::removePlaceholderCovers(_databasePath, *placeholderCovers); + if (!removed) + return; + if (typeid(*item) == typeid(LabelItem)) { + beginRemoveRows(mi.parent(), mi.row(), mi.row()); + labels.removeOne(static_cast(item)); endRemoveRows(); + delete item; + return; } + + if (typeid(*item) == typeid(ReadingListItem)) { + auto rli = static_cast(item); + if (rli->parent != rootItem) { + beginRemoveRows(mi.parent(), mi.row(), mi.row()); + rli->parent->removeChild(rli); + reorderingChildren(rli->parent->children()); + endRemoveRows(); + return; + } + } + + removeChild(item); } const QList ReadingListModel::getLabels() @@ -559,6 +685,8 @@ void ReadingListModel::cleanAll() { if (rootItem != 0) { delete rootItem; + delete smartListsRoot; + delete readingListsRoot; qDeleteAll(specialLists); qDeleteAll(labels); @@ -570,36 +698,25 @@ void ReadingListModel::cleanAll() } rootItem = 0; + smartListsRoot = nullptr; + readingListsRoot = nullptr; } -void ReadingListModel::setupReadingListsData(QSqlQuery &sqlquery, ReadingListItem *parent) +void ReadingListModel::setupReadingListsData(const QList<::ReadingList> &lists, ReadingListItem *parent, const QHash &folders) { items.insert(parent->getId(), parent); - QSqlRecord record = sqlquery.record(); - - int name = record.indexOf("name"); - int id = record.indexOf("id"); - int finished = record.indexOf("finished"); - int completed = record.indexOf("completed"); - int ordering = record.indexOf("ordering"); - int parentId = record.indexOf("parentId"); - - while (sqlquery.next()) { - ReadingListItem *rli = new ReadingListItem(QList() - << sqlquery.value(name) - << sqlquery.value(id) - << sqlquery.value(finished) - << sqlquery.value(completed) - << sqlquery.value(ordering)); - - ReadingListItem *currentParent; - if (sqlquery.value(parentId).isNull()) - currentParent = rootItem; - else - currentParent = items.value(sqlquery.value(parentId).toULongLong()); + for (const auto &list : lists) { + ReadingListItem *rli = new ReadingListItem(list); - currentParent->appendChild(rli); + if (!rli->list.parentId) { + // a top level list is in its folder, or at the top level of the section + rli->parent = rootItem; + rli->container = rli->list.folderId ? folders.value(*rli->list.folderId, readingListsRoot) : readingListsRoot; + rli->container->appendList(rli); + } else { + items.value(rli->list.parentId->value())->appendChild(rli); + } items.insert(rli->getId(), rli); } @@ -617,35 +734,19 @@ QList ReadingListModel::setupSpecialLists(QSqlDatabase &db) int id = record.indexOf("id"); while (selectQuery.next()) { - list << new SpecialListItem(QList() - << selectQuery.value(name) - << selectQuery.value(id)); + list << new SpecialListItem(::SpecialList { selectQuery.value(id).toULongLong(), selectQuery.value(name).toString() }); } - list.insert(1, new SpecialListItem(QList() << "Reading" << 0)); - list.insert(2, new SpecialListItem(QList() << "Recent" << 2)); + list.insert(1, new SpecialListItem(::SpecialList { 0, "Reading" })); + list.insert(2, new SpecialListItem(::SpecialList { 2, "Recent" })); return list; } void ReadingListModel::setupLabels(QSqlDatabase &db) { - QSqlQuery selectQuery("SELECT * FROM label ORDER BY ordering,name", db); - - QSqlRecord record = selectQuery.record(); - - int name = record.indexOf("name"); - int color = record.indexOf("color"); - int id = record.indexOf("id"); - int ordering = record.indexOf("ordering"); - - while (selectQuery.next()) { - addLabelIntoList(new LabelItem(QList() - << selectQuery.value(name) - << selectQuery.value(color) - << selectQuery.value(id) - << selectQuery.value(ordering))); - } + for (const auto &label : DBHelper::loadLabels(db).value_or(QList<::Label>())) + labels.append(new LabelItem(label)); // TEST @@ -663,15 +764,59 @@ void ReadingListModel::setupLabels(QSqlDatabase &db) // INSERT INTO label (name, color, ordering) VALUES ("put a smile on my face", "dark", 12); } +void ReadingListModel::setupSmartLists(QSqlDatabase &db) +{ + smartListsRoot = new ListFolderItem(ListFolder { { ListFolderKind::SmartList, 0 }, std::nullopt, QString(), 0 }); + const auto folders = setupListFolders(ListFolderKind::SmartList, smartListsRoot, db); + + if (const auto lists = DBHelper::loadSmartLists(db)) { + for (const auto &list : *lists) { + auto item = new SmartListItem(list); + item->container = list.folderId ? folders.value(*list.folderId, smartListsRoot) : smartListsRoot; + item->container->appendList(item); + } + } + sortByName(smartListsRoot, folders); +} + +void ReadingListModel::sortByName(ListFolderItem *root, const QHash &folders) +{ + root->sortByName(); + for (auto *folder : folders) + folder->sortByName(); +} + +QHash ReadingListModel::setupListFolders(ListFolderKind kind, ListFolderItem *root, QSqlDatabase &db) +{ + QHash folders; + const auto loaded = DBHelper::loadListFolders(kind, db); + if (!loaded) + return folders; + + // every folder first, so each one finds its parent + for (const auto &folder : *loaded) + folders.insert(folder.id.id, new ListFolderItem(folder)); + for (const auto &folder : *loaded) { + auto *container = folder.parentId ? folders.value(*folder.parentId, root) : root; + container->appendFolder(folders.value(folder.id.id)); + } + return folders; +} + void ReadingListModel::setupReadingLists(QSqlDatabase &db) { // setup root item - rootItem = new ReadingListItem(QList() << "ROOT" << 0 << true << false); - - QSqlQuery selectQuery("select * from reading_list order by parentId IS NULL DESC", db); + ::ReadingList root; + root.name = "ROOT"; + root.finished = true; + root.completed = false; + rootItem = new ReadingListItem(root); + readingListsRoot = new ListFolderItem(ListFolder { { ListFolderKind::ReadingList, 0 }, std::nullopt, QString(), 0 }); + const auto folders = setupListFolders(ListFolderKind::ReadingList, readingListsRoot, db); // setup reading lists - setupReadingListsData(selectQuery, rootItem); + setupReadingListsData(DBHelper::loadReadingLists(db).value_or(QList<::ReadingList>()), rootItem, folders); + sortByName(readingListsRoot, folders); // TEST // ReadingListItem * node1; @@ -730,6 +875,22 @@ void ReadingListModel::reorderingChildren(QList children) QSqlDatabase::removeDatabase(connectionName); } +int ReadingListModel::labelsStart() const +{ + return specialLists.count() + 1; +} + +int ReadingListModel::smartListsStart() const +{ + return labelsStart() + labels.count() + 1; +} + +int ReadingListModel::readingListsStart() const +{ + const auto smartRows = smartListsRoot->childCount(); + return smartListsStart() + smartRows + (smartRows > 0 ? 1 : 0); +} + bool ReadingListModel::rowIsSpecialList(int row, const QModelIndex &parent) const { if (parent.isValid()) @@ -746,38 +907,252 @@ bool ReadingListModel::rowIsLabel(int row, const QModelIndex &parent) const if (parent.isValid()) return false; // by now no sublists in labels - if (row > specialLists.count() && row <= specialLists.count() + labels.count()) - return true; + return row >= labelsStart() && row < labelsStart() + labels.count(); +} - return false; +QModelIndex ReadingListModel::indexOf(int type, qulonglong id) const +{ + // depth first over the rows, child lists included + std::function find = [&](const QModelIndex &parent) -> QModelIndex { + for (int row = 0; row < rowCount(parent); ++row) { + const auto child = index(row, 0, parent); + if (data(child, TypeListsRole).toInt() == type && data(child, IDRole).toULongLong() == id) + return child; + if (const auto found = find(child); found.isValid()) + return found; + } + return QModelIndex(); + }; + return find(QModelIndex()); } -bool ReadingListModel::rowIsReadingList(int row, const QModelIndex &parent) const +ListFolderItem *ReadingListModel::containerOf(ListItem *item) const { - if (parent.isValid()) - return true; // only lists with sublists + if (typeid(*item) == typeid(ListFolderItem)) + return static_cast(item)->container; + if (typeid(*item) == typeid(SmartListItem)) + return static_cast(item)->container; + if (typeid(*item) == typeid(ReadingListItem)) + return static_cast(item)->container; + return nullptr; +} - int separatorsCount = labels.isEmpty() ? 1 : 2; +QModelIndex ReadingListModel::indexOfItem(ListItem *item) const +{ + if (typeid(*item) == typeid(ReadingListItem)) { + auto rli = static_cast(item); + if (rli->parent != rootItem) + return createIndex(rli->parent->children().indexOf(rli), 0, rli); + } - if (row >= specialLists.count() + labels.count() + separatorsCount) - return true; + auto *container = containerOf(item); + return createIndex(firstRow(container) + container->rowOf(item), 0, item); +} - return false; +QModelIndex ReadingListModel::containerIndex(ListFolderItem *container) const +{ + return container->container == nullptr ? QModelIndex() : indexOfItem(container); } -bool ReadingListModel::rowIsSeparator(int row, const QModelIndex &parent) const +int ReadingListModel::firstRow(ListFolderItem *container) const { - if (parent.isValid()) - return false; // only separators at top level + if (container == smartListsRoot) + return smartListsStart(); + if (container == readingListsRoot) + return readingListsStart(); + return 0; +} - if (row == specialLists.count()) - return true; +ListFolderItem *ReadingListModel::containerFor(ListFolderKind kind, const QModelIndex &folder) const +{ + const auto id = listFolder(folder); + if (id && id->kind == kind) + return static_cast(folder.internalPointer()); + return kind == ListFolderKind::ReadingList ? readingListsRoot : smartListsRoot; +} - int separatorsCount = labels.isEmpty() ? 1 : 2; - if (separatorsCount == 2 && row == specialLists.count() + labels.count() + 1) - return true; +// the first row of the smart lists section also brings the separator before the reading lists +void ReadingListModel::insertChild(ListFolderItem *container, int childRow, const std::function &insert) +{ + const auto row = firstRow(container) + childRow; + const bool withSeparator = container == smartListsRoot && container->childCount() == 0; + beginInsertRows(containerIndex(container), row, withSeparator ? row + 1 : row); + insert(); + endInsertRows(); +} - return false; +void ReadingListModel::removeChild(ListItem *item) +{ + auto *container = containerOf(item); + const auto parent = containerIndex(container); + const auto row = firstRow(container) + container->rowOf(item); + const bool withSeparator = container == smartListsRoot && container->childCount() == 1; + beginRemoveRows(parent, row, withSeparator ? row + 1 : row); + container->removeChild(item); + endRemoveRows(); + delete item; +} + +// a move stays in its section, so the smart lists section never gains or loses its first row (and its separator) here +void ReadingListModel::moveChild(ListItem *item, ListFolderItem *to) +{ + auto *from = containerOf(item); + const bool folder = typeid(*item) == typeid(ListFolderItem); + const auto fromRow = firstRow(from) + from->rowOf(item); + // the rows of `to` without the item + const auto toRow = firstRow(to) + (folder ? to->rowForFolderName(item->name(), item) : to->rowForListName(item->name(), item)); + + if (from != to || toRow != fromRow) { + // in the same parent a row that goes down is announced before the row that follows its new place + beginMoveRows(containerIndex(from), fromRow, fromRow, containerIndex(to), from == to && toRow > fromRow ? toRow + 1 : toRow); + from->removeChild(item); + const auto folderId = to->container == nullptr ? std::nullopt : std::optional(to->getId()); + if (folder) { + static_cast(item)->folder.parentId = folderId; + to->insertFolderByName(static_cast(item)); + } else if (typeid(*item) == typeid(SmartListItem)) { + static_cast(item)->list.folderId = folderId; + static_cast(item)->container = to; + to->insertListByName(item); + } else { + static_cast(item)->list.folderId = folderId; + static_cast(item)->container = to; + to->insertListByName(item); + } + endMoveRows(); + } + + const auto mi = indexOfItem(item); + emit dataChanged(mi, mi); +} + +bool ReadingListModel::canMove(ListItem *item, ListFolderItem *to) const +{ + auto *from = containerOf(item); + if (from == nullptr || to == nullptr || from == to || from->folder.id.kind != to->folder.id.kind) + return false; + for (auto *folder = to; folder != nullptr; folder = folder->container) { + if (folder == item) + return false; + } + return true; +} + +bool ReadingListModel::moveItem(ListItem *item, ListFolderItem *to) +{ + if (!canMove(item, to)) + return false; + + const auto folderId = to->container == nullptr ? std::nullopt : std::optional(to->getId()); + bool moved = false; + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath); + if (typeid(*item) == typeid(ListFolderItem)) + moved = DBHelper::moveListFolder(static_cast(item)->folder.id, folderId, db); + else if (typeid(*item) == typeid(SmartListItem)) + moved = DBHelper::moveSmartListToFolder(static_cast(item)->list.id, folderId, db); + else + moved = DBHelper::moveReadingListToFolder(static_cast(item)->list.id, folderId, db); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + if (!moved) + return false; + + moveChild(item, to); + return true; +} + +bool ReadingListModel::canMoveToFolder(const QModelIndex &mi, const QModelIndex &folder) const +{ + const auto kind = section(mi); + if (!kind) + return false; + if (folder.isValid()) { + const auto id = listFolder(folder); + if (!id || id->kind != *kind) + return false; + } + return canMove(static_cast(mi.internalPointer()), containerFor(*kind, folder)); +} + +bool ReadingListModel::moveToFolder(const QModelIndex &mi, const QModelIndex &folder) +{ + if (!canMoveToFolder(mi, folder)) + return false; + return moveItem(static_cast(mi.internalPointer()), containerFor(*section(mi), folder)); +} + +bool ReadingListModel::isChildList(const ListItem *item) const +{ + return typeid(*item) == typeid(ReadingListItem) && static_cast(item)->parent != rootItem; +} + +ListItem *ReadingListModel::draggedItem(const QMimeData *data) const +{ + QByteArray rawData = data->data(YACReader::YACReaderLibraryListRowMimeDataFormat); + QDataStream in(&rawData, QIODevice::ReadOnly); + int type = -1; + qulonglong id = 0; + in >> type >> id; + if (in.status() != QDataStream::Ok) + return nullptr; + + const auto mi = indexOf(type, id); + return mi.isValid() ? static_cast(mi.internalPointer()) : nullptr; +} + +// onto a row, or between rows: the row after the gap, else the row before it +QModelIndexList ReadingListModel::dropTargets(int row, const QModelIndex &parent) const +{ + if (row == -1) + return { parent }; + return { index(row, 0, parent), index(row - 1, 0, parent) }; +} + +ListFolderItem *ReadingListModel::dropContainer(ListItem *item, int row, const QModelIndex &parent) const +{ + auto *from = containerOf(item); + if (from == nullptr) + return nullptr; + + if (row == -1 && !parent.isValid()) { + auto *root = from->folder.id.kind == ListFolderKind::ReadingList ? readingListsRoot : smartListsRoot; + return canMove(item, root) ? root : nullptr; + } + + for (const auto &target : dropTargets(row, parent)) { + if (!target.isValid()) + continue; + auto *targetItem = static_cast(target.internalPointer()); + ListFolderItem *to = nullptr; + if (row == -1 && typeid(*targetItem) == typeid(ListFolderItem)) + to = static_cast(targetItem); + else if (typeid(*targetItem) == typeid(ListFolderItem) || typeid(*targetItem) == typeid(SmartListItem) || typeid(*targetItem) == typeid(ReadingListItem)) + to = containerOf(targetItem); // nothing for a child list + if (canMove(item, to)) + return to; + } + return nullptr; +} + +ReadingListItem *ReadingListModel::childListDropTarget(ReadingListItem *item, int row, const QModelIndex &parent) const +{ + for (const auto &target : dropTargets(row, parent)) { + if (!target.isValid()) + continue; + auto *targetItem = static_cast(target.internalPointer()); + if (targetItem != item && typeid(*targetItem) == typeid(ReadingListItem) && static_cast(targetItem)->parent == item->parent) + return static_cast(targetItem); + } + return nullptr; +} + +// a dragged row is dropped by the base view only when the model names its format +QStringList ReadingListModel::mimeTypes() const +{ + return { YACReader::YACReaderLibraryListRowMimeDataFormat }; } ReadingListModelProxy::ReadingListModelProxy(QObject *parent) diff --git a/YACReaderLibrary/db/reading_list_model.h b/YACReaderLibrary/db/reading_list_model.h index 7f5efb515..ef35703af 100644 --- a/YACReaderLibrary/db/reading_list_model.h +++ b/YACReaderLibrary/db/reading_list_model.h @@ -1,19 +1,29 @@ #ifndef READING_LIST_MODEL_H #define READING_LIST_MODEL_H +#include "library_ids.h" +#include "smart_list.h" #include "yacreader_global.h" #include +#include #include #include #include #include #include +#include +#include + class LabelItem; +class ListFolderItem; +class ListItem; +class SmartListItem; class SpecialListItem; class ReadingListItem; class ReadingListSeparatorItem; +struct ReadingList; class ReadingListModelProxy : public QSortFilterProxyModel { @@ -27,6 +37,7 @@ class ReadingListModel : public QAbstractItemModel Q_OBJECT public: explicit ReadingListModel(QObject *parent = nullptr); + ~ReadingListModel() override; // QAbstractItemModel methods int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -41,15 +52,38 @@ class ReadingListModel : public QAbstractItemModel bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; QMimeData *mimeData(const QModelIndexList &indexes) const override; + QStringList mimeTypes() const override; // Convenience methods void setupReadingListsData(QString path); void addNewLabel(const QString &name, YACReader::LabelColors color); - void addReadingList(const QString &name); // top level reading list + // a top level reading list, in `folder` when it is a reading list folder + void addReadingList(const QString &name, const QModelIndex &folder = QModelIndex()); + // a top level reading list that is already in the database + void appendReadingList(ReadingListId id, const QString &name); void addReadingListAt(const QString &name, const QModelIndex &mi); bool isEditable(const QModelIndex &mi); bool isReadingList(const QModelIndex &mi); bool isReadingSubList(const QModelIndex &mi); + // in `folder` when it is a smart list folder, else at the top level; the index of its row, invalid when it cannot be + // stored + QModelIndex addSmartList(const QString &name, const QString &query, const QModelIndex &folder = QModelIndex()); + // nothing when the row is not a smart list + std::optional<::SmartList> smartList(const QModelIndex &mi) const; + bool updateSmartList(const QModelIndex &mi, const QString &name, const QString &query); + // in `parentFolder` (a folder of `kind`), else at the top level of the section; the index of its row, invalid when + // it cannot be stored + QModelIndex addListFolder(ListFolderKind kind, const QModelIndex &parentFolder, const QString &name); + // a folder or a list (not a child list) into `folder`, a folder of its section, or to the top level of its section + // when `folder` is invalid; never into its current place, nor a folder into itself or a folder below it + bool canMoveToFolder(const QModelIndex &mi, const QModelIndex &folder) const; + bool moveToFolder(const QModelIndex &mi, const QModelIndex &folder); + // nothing when the row is not a folder of lists + std::optional listFolder(const QModelIndex &mi) const; + // the section of a list or a folder of lists: reading lists or smart lists; nothing for the other rows + std::optional section(const QModelIndex &mi) const; + // the row of a list or a folder of lists (`type` is a TypeList); invalid when there is none + QModelIndex indexOf(int type, qulonglong id) const; QString name(const QModelIndex &mi); void rename(const QModelIndex &mi, const QString &name); void deleteItem(const QModelIndex &mi); @@ -66,7 +100,10 @@ class ReadingListModel : public QAbstractItemModel SpecialList, Label, ReadingList, - Separator + Separator, + SmartList, + ReadingListFolder, + SmartListFolder }; enum class TypeSpecialList : int { @@ -78,38 +115,78 @@ class ReadingListModel : public QAbstractItemModel signals: void addComicsToFavorites(const QList &comicIds); - void addComicsToLabel(const QList &comicIds, qulonglong labelId); - void addComicsToReadingList(const QList &comicIds, qulonglong readingListId); + void addComicsToLabel(const QList &rows, qulonglong labelId); + void addComicsToReadingList(const QList &rows, qulonglong readingListId); private: void cleanAll(); - void setupReadingListsData(QSqlQuery &sqlquery, ReadingListItem *parent); + void setupReadingListsData(const QList<::ReadingList> &lists, ReadingListItem *parent, const QHash &folders); QList setupSpecialLists(QSqlDatabase &db); void setupLabels(QSqlDatabase &db); + void setupSmartLists(QSqlDatabase &db); void setupReadingLists(QSqlDatabase &db); + // the root of the section with its folders; the folders by id + QHash setupListFolders(ListFolderKind kind, ListFolderItem *root, QSqlDatabase &db); + // after a load, each folder of a section once + static void sortByName(ListFolderItem *root, const QHash &folders); int addLabelIntoList(LabelItem *item); void reorderingChildren(QList children); + // top level rows: special lists, a separator, labels, a separator, the smart lists section and a separator when it + // has rows, the reading lists section + int labelsStart() const; + int smartListsStart() const; + int readingListsStart() const; bool rowIsSpecialList(int row, const QModelIndex &parent = QModelIndex()) const; bool rowIsLabel(int row, const QModelIndex &parent = QModelIndex()) const; - bool rowIsReadingList(int row, const QModelIndex &parent = QModelIndex()) const; - bool rowIsSeparator(int row, const QModelIndex &parent = QModelIndex()) const; + // where an item is: its folder (or the root of its section) for folders, smart lists, and top level reading lists + ListFolderItem *containerOf(ListItem *item) const; + QModelIndex indexOfItem(ListItem *item) const; + // the parent index of the rows of a container, and the row of its first child + QModelIndex containerIndex(ListFolderItem *container) const; + int firstRow(ListFolderItem *container) const; + // the container of `folder` when it is a folder of `kind`, else the root of that section + ListFolderItem *containerFor(ListFolderKind kind, const QModelIndex &folder) const; + // `insert` puts the item at `childRow` of the container; the rows are announced around it + void insertChild(ListFolderItem *container, int childRow, const std::function &insert); + void removeChild(ListItem *item); + // the row of `item` goes to its place by name in `to`, a container of its section (its own container after a rename) + void moveChild(ListItem *item, ListFolderItem *to); + bool canMove(ListItem *item, ListFolderItem *to) const; + // in the database, then in the rows + bool moveItem(ListItem *item, ListFolderItem *to); + + bool isChildList(const ListItem *item) const; + // the row a drag of rows carries; nullptr when it is gone + ListItem *draggedItem(const QMimeData *data) const; + // `row` -1 drops onto `parent` (empty space when it is invalid), else between the rows of `parent` + QModelIndexList dropTargets(int row, const QModelIndex &parent) const; + // where a dropped folder or list goes: onto a folder, inside it; onto a list or between rows, next to that row; on + // empty space, the top level of its section; nullptr when it cannot go there + ListFolderItem *dropContainer(ListItem *item, int row, const QModelIndex &parent) const; + // another child list of the same parent, whose row a dropped child list takes; nullptr when there is none + ReadingListItem *childListDropTarget(ReadingListItem *item, int row, const QModelIndex &parent) const; bool dropComics(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - bool dropSublist(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + bool dropSublist(ReadingListItem *item, ReadingListItem *destination); // Special lists QList specialLists; // Label QList labels; - // Reading lists + // the roots of the two sections of folders and lists + ListFolderItem *smartListsRoot = nullptr; + ListFolderItem *readingListsRoot = nullptr; + + // the parent of every top level reading list (child lists have their parent list) ReadingListItem *rootItem; // QMap items; // lists relationship // separators ReadingListSeparatorItem *separator1; ReadingListSeparatorItem *separator2; + ReadingListSeparatorItem *separator3; QString _databasePath; }; diff --git a/YACReaderLibrary/db/search_query.cpp b/YACReaderLibrary/db/search_query.cpp index 0ede13fad..e8ce30e8d 100644 --- a/YACReaderLibrary/db/search_query.cpp +++ b/YACReaderLibrary/db/search_query.cpp @@ -1,17 +1,22 @@ #include "search_query.h" +#include "comic_db.h" +#include "folder.h" #include "query_parser.h" +#include "table_projection.h" #include #include +#include + QSqlQuery foldersSearchQuery(QSqlDatabase &db, const QString &filter) { QueryParser parser; auto result = parser.parse(filter.toStdString()); - std::string queryString(SEARCH_FOLDERS_QUERY); + std::string queryString = "SELECT DISTINCT " + TableProjection::selectList("f", Folder::columns()).toStdString() + " " SEARCH_FOLDERS_FROM; result.buildSqlString(queryString); queryString += " AND f.id <> 1 ORDER BY f.parentId,f.name"; @@ -24,21 +29,50 @@ QSqlQuery foldersSearchQuery(QSqlDatabase &db, const QString &filter) return selectQuery; } -QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter) +namespace { + +QSqlQuery comicsQuery(QSqlDatabase &db, const QString &filter, std::optional limit, std::optional folderId = std::nullopt) { QueryParser parser; auto result = parser.parse(filter.toStdString()); - std::string queryString(SEARCH_COMICS_QUERY); + std::string queryString = "SELECT " + ComicDB::selectList("c", "ci").toStdString() + " " SEARCH_COMICS_FROM; result.buildSqlString(queryString); - queryString += " LIMIT :limit"; + if (folderId) + queryString += " AND c.parentId = :folder_id"; + // near the order of the search results (numbers by value, text without case), so the limit always keeps the same comics + queryString += " ORDER BY (ci.series IS NULL OR TRIM(ci.series) = ''), TRIM(ci.series) COLLATE NOCASE, " + "(ci.volume IS NOT NULL AND TRIM(ci.volume) <> ''), TRIM(ci.volume) COLLATE NOCASE, " + "ci.number IS NULL, CAST(ci.number AS REAL), ci.number COLLATE NOCASE, c.fileName COLLATE NOCASE, c.path"; + if (limit) + queryString += " LIMIT :limit"; QSqlQuery selectQuery(db); selectQuery.prepare(queryString.c_str()); - selectQuery.bindValue(":limit", 500); // TODO, load this value from settings + if (limit) + selectQuery.bindValue(":limit", *limit); + if (folderId) + selectQuery.bindValue(":folder_id", *folderId); result.bindValues(selectQuery); selectQuery.exec(); return selectQuery; } + +} + +QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter) +{ + return comicsQuery(db, filter, 500); // TODO, load this value from settings +} + +QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter, qulonglong folderId) +{ + return comicsQuery(db, filter, 500, folderId); +} + +QSqlQuery allComicsSearchQuery(QSqlDatabase &db, const QString &filter) +{ + return comicsQuery(db, filter, std::nullopt); +} diff --git a/YACReaderLibrary/db/search_query.h b/YACReaderLibrary/db/search_query.h index 208100a54..436bc719e 100644 --- a/YACReaderLibrary/db/search_query.h +++ b/YACReaderLibrary/db/search_query.h @@ -5,6 +5,11 @@ #include QSqlQuery foldersSearchQuery(QSqlDatabase &db, const QString &filter); +// the first 500 comics that match; throws when the filter cannot be parsed QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter); +// the first 500 comics of the folder that match; throws when the filter cannot be parsed +QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter, qulonglong folderId); +// every comic that matches, for a smart list; throws when the filter cannot be parsed +QSqlQuery allComicsSearchQuery(QSqlDatabase &db, const QString &filter); #endif // SEARCHQUERY_H diff --git a/YACReaderLibrary/db/smart_list.cpp b/YACReaderLibrary/db/smart_list.cpp new file mode 100644 index 000000000..f90d51c4e --- /dev/null +++ b/YACReaderLibrary/db/smart_list.cpp @@ -0,0 +1,31 @@ +#include "smart_list.h" + +#include "table_projection.h" + +#include +#include + +const QStringList &SmartList::columns() +{ + static const QStringList columns = { "name", "query", "ordering", "folder_id" }; + return columns; +} + +QList SmartList::columnIndexes(const QSqlRecord &record, const QString &alias) +{ + return TableProjection::columnIndexes(record, alias, columns()); +} + +// the indexes are of {id} + columns() +SmartList SmartList::fromQuery(const QSqlQuery &query, const QList &columnIndexes) +{ + SmartList list; + list.id = SmartListId(query.value(columnIndexes.at(0)).toULongLong()); + list.name = query.value(columnIndexes.at(1)).toString(); + list.query = query.value(columnIndexes.at(2)).toString(); + list.ordering = query.value(columnIndexes.at(3)).toInt(); + const auto folderId = query.value(columnIndexes.at(4)); + if (!folderId.isNull()) + list.folderId = folderId.toULongLong(); + return list; +} diff --git a/YACReaderLibrary/db/smart_list.h b/YACReaderLibrary/db/smart_list.h new file mode 100644 index 000000000..c38b53ebd --- /dev/null +++ b/YACReaderLibrary/db/smart_list.h @@ -0,0 +1,29 @@ +#ifndef SMART_LIST_H +#define SMART_LIST_H + +#include "library_ids.h" + +#include +#include + +#include + +class QSqlQuery; +class QSqlRecord; + +// a saved search: its comics are the comics that match `query` now +struct SmartList { + SmartListId id; + QString name; + // in the search syntax of the database version; a change of the syntax rewrites it in the database update + QString query; + int ordering = 0; + // a smart list folder; nothing at the top level + std::optional folderId; + + static const QStringList &columns(); + static QList columnIndexes(const QSqlRecord &record, const QString &alias); + static SmartList fromQuery(const QSqlQuery &query, const QList &columnIndexes); +}; + +#endif diff --git a/YACReaderLibrary/db_helper.cpp b/YACReaderLibrary/db_helper.cpp index e0081f9e3..e5f7f4f40 100644 --- a/YACReaderLibrary/db_helper.cpp +++ b/YACReaderLibrary/db_helper.cpp @@ -3,10 +3,17 @@ #include "QsLog.h" #include "comic_db.h" #include "data_base_management.h" +#include "descriptive_comic_metadata.h" #include "folder.h" -#include "library_item.h" +#include "library_item_types.h" +#include "list_entry.h" +#include "list_folder.h" +#include "overloaded.h" +#include "placeholder.h" #include "qnaturalsorting.h" #include "reading_list.h" +#include "smart_list.h" +#include "table_projection.h" #include "yacreader_global.h" #include "yacreader_libraries.h" @@ -14,8 +21,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -24,6 +33,7 @@ #include #include +#include #include using namespace YACReader; @@ -37,11 +47,11 @@ YACReaderLibraries DBHelper::getLibraries() return libraries; } -QList DBHelper::getFolderSubfoldersFromLibrary(qulonglong libraryId, qulonglong folderId) +QList DBHelper::getFolderSubfoldersFromLibrary(qulonglong libraryId, qulonglong folderId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; - QList list; + QList list; { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); list = DBHelper::getFoldersFromParent(folderId, db, false); @@ -52,30 +62,27 @@ QList DBHelper::getFolderSubfoldersFromLibrary(qulonglong library return list; } -QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId) +QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId) { return DBHelper::getFolderComicsFromLibrary(libraryId, folderId, false); } -QList DBHelper::getFolderComicsFromLibraryForReading(qulonglong libraryId, qulonglong folderId) +QList DBHelper::getFolderComicsFromLibraryForReading(qulonglong libraryId, qulonglong folderId) { auto list = DBHelper::getFolderComicsFromLibrary(libraryId, folderId, false); - std::sort(list.begin(), list.end(), [](LibraryItem *i1, LibraryItem *i2) { - auto c1 = static_cast(i1); - auto c2 = static_cast(i2); - - return comicNumberLessThan(c1->info.number, c1->name, c2->info.number, c2->name); + std::sort(list.begin(), list.end(), [](const ComicDB &c1, const ComicDB &c2) { + return comicNumberLessThan(c1.info.number, c1.file.fileName, c2.info.number, c2.file.fileName); }); return list; } -QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId, bool sort) +QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, qulonglong folderId, bool sort) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; - QList list; + QList list; { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); list = DBHelper::getComicsFromParent(folderId, db, sort); @@ -86,161 +93,419 @@ QList DBHelper::getFolderComicsFromLibrary(qulonglong libraryId, return list; } -quint32 DBHelper::getNumChildrenFromFolder(qulonglong libraryId, qulonglong folderId) +std::optional DBHelper::getComicInfo(qulonglong libraryId, qulonglong id) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); - quint32 result = 0; QString connectionName = ""; - + std::optional comic; { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT count(*) FROM folder WHERE parentId = :parentId and id <> 1"); - selectQuery.bindValue(":parentId", folderId); - selectQuery.exec(); - - result += selectQuery.record().value(0).toULongLong(); - - selectQuery.prepare("SELECT count(*) FROM comic c WHERE c.parentId = :parentId"); - selectQuery.bindValue(":parentId", folderId); - selectQuery.exec(); - - result += selectQuery.record().value(0).toULongLong(); + bool found; + auto loaded = DBHelper::loadComic(id, db, found); + if (found) { + comic = loaded; + } connectionName = db.connectionName(); } - QSqlDatabase::removeDatabase(connectionName); - - return result; + return comic; } -qulonglong DBHelper::getParentFromComicFolderId(qulonglong libraryId, qulonglong id) +QList DBHelper::getSiblings(qulonglong libraryId, qulonglong parentId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; - Folder f; + QList comics; { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - - f = DBHelper::loadFolder(id, db); + comics = DBHelper::getSortedComicsFromParent(parentId, db); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); - return f.parentId; + return comics; } -ComicDB DBHelper::getComicInfo(qulonglong libraryId, qulonglong id) + +std::optional DBHelper::getFolder(qulonglong libraryId, qulonglong id) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); + + std::optional folder; QString connectionName = ""; - ComicDB comic; + { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - - bool found; - comic = DBHelper::loadComic(id, db, found); + folder = DBHelper::loadFolder(id, db); connectionName = db.connectionName(); } + QSqlDatabase::removeDatabase(connectionName); - return comic; + return folder; } -QList DBHelper::getSiblings(qulonglong libraryId, qulonglong parentId) +namespace { + +QList comicsFromQuery(QSqlQuery &query) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryId); - QString connectionName = ""; QList comics; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - comics = DBHelper::getSortedComicsFromParent(parentId, db); - connectionName = db.connectionName(); + const auto indexes = ComicDB::columnIndexes(query.record(), "c", "ci"); + while (query.next()) { + comics.append(ComicDB::fromQuery(query, indexes)); } - - QSqlDatabase::removeDatabase(connectionName); return comics; } -QString DBHelper::getFolderName(qulonglong libraryId, qulonglong id) +} + +namespace { + +struct EntryTable { + const char *table; + const char *parentColumn; + ListKind listKind; + bool repeats; +}; + +constexpr EntryTable readingListEntries { "reading_list_entry", "reading_list_id", ListKind::ReadingList, true }; +constexpr EntryTable labelEntries { "label_entry", "label_id", ListKind::Label, false }; + +// a comic_info with several files shows the first one +QString listComicsQuery(const EntryTable &entries) +{ + return "SELECT " + ComicDB::selectList("c", "ci") + ", " + TableProjection::selectList("e", ListEntry::columns(entries.listKind)) + + QString(" FROM %1 e " + "INNER JOIN comic c ON (c.id = (SELECT MIN(id) FROM comic WHERE comicInfoId = e.comic_info_id)) " + "INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " + "WHERE e.%2 = :list_id " + "ORDER BY e.ordering, e.id") + .arg(entries.table, entries.parentColumn); +} + +// an entry refers to a comic_info (with or without a file) or to a placeholder +QString listEntriesQuery(const EntryTable &entries) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryId); + return "SELECT " + ComicDB::selectList("c", "ci") + ", " + TableProjection::selectList("e", ListEntry::columns(entries.listKind)) + ", " + + TableProjection::selectList("p", Placeholder::columns()) + + QString(" FROM %1 e " + "LEFT JOIN comic_info ci ON (ci.id = e.comic_info_id) " + "LEFT JOIN comic c ON (c.id = (SELECT MIN(id) FROM comic WHERE comicInfoId = e.comic_info_id)) " + "LEFT JOIN placeholder p ON (p.id = e.placeholder_id) " + "WHERE e.%2 = :list_id " + "ORDER BY e.ordering, e.id") + .arg(entries.table, entries.parentColumn); +} - QString name = ""; - QString connectionName = ""; +bool allOfKind(const QList &entryIds, ListKind listKind) +{ + return std::all_of(entryIds.begin(), entryIds.end(), [listKind](const ListItemId &id) { return id.listKind == listKind; }); +} - { - QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - QSqlQuery selectQuery(db); // TODO check - selectQuery.prepare("SELECT name FROM folder WHERE id = :id"); - selectQuery.bindValue(":id", id); - selectQuery.exec(); +// a placeholder lives while an entry or a folder placement uses it +QString placeholderUnused() +{ + return QString("NOT EXISTS (SELECT 1 FROM %1 WHERE placeholder_id = p.id) " + "AND NOT EXISTS (SELECT 1 FROM %2 WHERE placeholder_id = p.id) " + "AND NOT EXISTS (SELECT 1 FROM folder_placeholder WHERE placeholder_id = p.id)") + .arg(readingListEntries.table, labelEntries.table); +} - if (selectQuery.next()) { - name = selectQuery.value(0).toString(); +// deletes the placeholders of `placeholderIds` that nothing uses; gives their cover keys +std::optional deleteUnusedPlaceholders(const QSet &placeholderIds, QSqlDatabase &db) +{ + QSqlQuery select(db); + select.prepare("SELECT p.coverKey FROM placeholder p WHERE p.id = :id AND " + placeholderUnused()); + QSqlQuery remove(db); + remove.prepare("DELETE FROM placeholder WHERE id = :id"); + + QStringList coverKeys; + for (const auto placeholderId : placeholderIds) { + select.bindValue(":id", placeholderId); + if (!select.exec()) { + QLOG_ERROR() << "Unable to find unused placeholders:" << select.lastError().text(); + return std::nullopt; + } + if (!select.next()) { + continue; + } + const auto coverKey = select.value(0); + select.finish(); + + remove.bindValue(":id", placeholderId); + if (!remove.exec()) { + QLOG_ERROR() << "Unable to delete an unused placeholder:" << remove.lastError().text(); + return std::nullopt; + } + if (!coverKey.isNull()) { + coverKeys.append(coverKey.toString()); } - connectionName = db.connectionName(); } + return coverKeys; +} - QSqlDatabase::removeDatabase(connectionName); - return name; +// runs `removal` in a transaction and then deletes the placeholders of `placeholderIds` that nothing uses any more +// inside a transaction of the caller the BEGIN fails, and the commit here would commit the caller's work early +bool beginTransaction(QSqlDatabase &db) +{ + if (db.transaction()) + return true; + QLOG_ERROR() << "Unable to begin a transaction:" << db.lastError().text(); + return false; } -Folder DBHelper::getFolder(qulonglong libraryId, qulonglong id) +std::optional removeWithPlaceholders(QSqlQuery &placeholderIds, const std::function &removal, QSqlDatabase &db) { - QString libraryPath = DBHelper::getLibraries().getPath(libraryId); + if (!beginTransaction(db)) + return std::nullopt; - Folder folder; - QString connectionName = ""; + QSet ids; + if (!placeholderIds.exec()) { + QLOG_ERROR() << "Unable to read the placeholders of the removed entries:" << placeholderIds.lastError().text(); + db.rollback(); + return std::nullopt; + } + while (placeholderIds.next()) { + ids.insert(placeholderIds.value(0).toULongLong()); + } + placeholderIds.finish(); - { - QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - QSqlQuery selectQuery(db); // TODO check - selectQuery.prepare("SELECT * FROM folder WHERE id = :id"); - selectQuery.bindValue(":id", id); - selectQuery.exec(); + if (!removal()) { + db.rollback(); + return std::nullopt; + } + + auto coverKeys = deleteUnusedPlaceholders(ids, db); + if (!coverKeys) { + db.rollback(); + return std::nullopt; + } - auto record = selectQuery.record(); - - int name = record.indexOf("name"); - int path = record.indexOf("path"); - int finished = record.indexOf("finished"); - int completed = record.indexOf("completed"); - int id = record.indexOf("id"); - int parentId = record.indexOf("parentId"); - int numChildren = record.indexOf("numChildren"); - int firstChildHash = record.indexOf("firstChildHash"); - int customImage = record.indexOf("customImage"); - int type = record.indexOf("type"); - int added = record.indexOf("added"); - int updated = record.indexOf("updated"); - - if (selectQuery.next()) { - folder = Folder(selectQuery.value(id).toULongLong(), selectQuery.value(parentId).toULongLong(), selectQuery.value(name).toString(), selectQuery.value(path).toString()); - - folder.finished = selectQuery.value(finished).toBool(); - folder.completed = selectQuery.value(completed).toBool(); - if (!selectQuery.value(numChildren).isNull() && selectQuery.value(numChildren).isValid()) { - folder.numChildren = selectQuery.value(numChildren).toInt(); + if (!db.commit()) { + return std::nullopt; + } + return coverKeys; +} + +std::optional removeEntries(const EntryTable &entries, qulonglong parentId, const QList &entryIds, QSqlDatabase &db) +{ + if (!allOfKind(entryIds, entries.listKind)) { + return std::nullopt; + } + + QStringList ids; + for (const auto &entryId : entryIds) { + ids.append(QString::number(entryId.id)); + } + + QSqlQuery placeholderIds(db); + placeholderIds.prepare(QString("SELECT DISTINCT placeholder_id FROM %1 WHERE placeholder_id IS NOT NULL AND %2 = :parent_id AND id IN (%3)") + .arg(entries.table, entries.parentColumn, ids.join(","))); + placeholderIds.bindValue(":parent_id", parentId); + + const auto removal = [&] { + QSqlQuery query(db); + query.prepare(QString("DELETE FROM %1 WHERE id = :id AND %2 = :parent_id").arg(entries.table, entries.parentColumn)); + + for (const auto &entryId : entryIds) { + query.bindValue(":id", entryId.id); + query.bindValue(":parent_id", parentId); + if (!query.exec()) { + QLOG_ERROR() << "Unable to remove entries from" << entries.table << ":" << query.lastError().text(); + return false; } - folder.firstChildHash = selectQuery.value(firstChildHash).toString(); - folder.customImage = selectQuery.value(customImage).toString(); - folder.type = selectQuery.value(type).value(); - folder.added = selectQuery.value(added).toLongLong(); - folder.updated = selectQuery.value(updated).toLongLong(); } - connectionName = db.connectionName(); + return true; + }; + return removeWithPlaceholders(placeholderIds, removal, db); +} + +bool reorderEntries(const EntryTable &entries, qulonglong parentId, const QList &entryIds, QSqlDatabase &db) +{ + if (!allOfKind(entryIds, entries.listKind) || QSet(entryIds.begin(), entryIds.end()).size() != entryIds.size()) { + return false; } - QSqlDatabase::removeDatabase(connectionName); - return folder; + if (!beginTransaction(db)) + return false; + + QSqlQuery count(db); + count.prepare(QString("SELECT COUNT(*) FROM %1 WHERE %2 = :parent_id").arg(entries.table, entries.parentColumn)); + count.bindValue(":parent_id", parentId); + const bool allEntriesGiven = count.exec() && count.next() && count.value(0).toLongLong() == entryIds.size(); + count.finish(); + if (!allEntriesGiven) { + db.rollback(); + return false; + } + + QSqlQuery query(db); + query.prepare(QString("UPDATE %1 SET ordering = :ordering WHERE id = :id AND %2 = :parent_id").arg(entries.table, entries.parentColumn)); + + int ordering = 0; + for (const auto &entryId : entryIds) { + query.bindValue(":ordering", ordering++); + query.bindValue(":id", entryId.id); + query.bindValue(":parent_id", parentId); + if (!query.exec() || query.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to reorder entries of" << entries.table << ":" << query.lastError().text(); + db.rollback(); + return false; + } + } + + return db.commit(); } -QList DBHelper::getLibrariesNames() +bool appendComics(const EntryTable &entries, qulonglong parentId, const QList &comicInfoIds, QSqlDatabase &db) { - auto names = getLibraries().getNames(); - std::sort(names.begin(), names.end(), naturalSortLessThanCI); - return names; + db.transaction(); + + QSqlQuery query(db); + query.prepare(QString("INSERT INTO %1 (%2, ordering, comic_info_id) " + "SELECT :parent_id, " + "(SELECT COALESCE(MAX(ordering), -1) + 1 FROM %1 WHERE %2 = :parent_id), " + "ci.id " + "FROM comic_info ci " + "WHERE ci.id = :comic_info_id") + .arg(entries.table, entries.parentColumn) + + (entries.repeats ? QString() : QString(" AND NOT EXISTS (SELECT 1 FROM %1 WHERE %2 = :parent_id AND comic_info_id = ci.id)").arg(entries.table, entries.parentColumn))); + + for (const auto comicInfoId : comicInfoIds) { + query.bindValue(":parent_id", parentId); + query.bindValue(":comic_info_id", comicInfoId.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to append comics to" << entries.table << ":" << query.lastError().text(); + db.rollback(); + return false; + } + } + + return db.commit(); +} + +// appends what each row refers to, in order: the comic_info of a comic, the comic_info or placeholder of an entry, or +// a placeholder of a folder +bool appendRows(const EntryTable &entries, qulonglong parentId, const QList &rows, QSqlDatabase &db) +{ + const auto insert = [&entries](const QString &source) { + auto statement = QString("INSERT INTO %1 (%2, ordering, comic_info_id, placeholder_id) " + "SELECT :parent_id, " + "(SELECT COALESCE(MAX(ordering), -1) + 1 FROM %1 WHERE %2 = :parent_id), " + "s.comic_info_id, s.placeholder_id " + "FROM (%3) s") + .arg(entries.table, entries.parentColumn, source); + if (!entries.repeats) + statement += QString(" WHERE NOT EXISTS (SELECT 1 FROM %1 WHERE %2 = :parent_id AND comic_info_id IS s.comic_info_id AND placeholder_id IS s.placeholder_id)") + .arg(entries.table, entries.parentColumn); + return statement; + }; + + if (!beginTransaction(db)) + return false; + + QSqlQuery fromComic(db); + fromComic.prepare(insert("SELECT comicInfoId AS comic_info_id, NULL AS placeholder_id FROM comic WHERE id = :row_id")); + QSqlQuery fromReadingListEntry(db); + fromReadingListEntry.prepare(insert(QString("SELECT comic_info_id, placeholder_id FROM %1 WHERE id = :row_id").arg(readingListEntries.table))); + QSqlQuery fromLabelEntry(db); + fromLabelEntry.prepare(insert(QString("SELECT comic_info_id, placeholder_id FROM %1 WHERE id = :row_id").arg(labelEntries.table))); + QSqlQuery fromPlaceholder(db); + fromPlaceholder.prepare(insert("SELECT NULL AS comic_info_id, id AS placeholder_id FROM placeholder WHERE id = :row_id")); + + for (const auto &row : rows) { + auto &query = std::visit(overloaded { + [&](const ComicId &) -> QSqlQuery & { return fromComic; }, + [&](const ListItemId &entry) -> QSqlQuery & { return entry.listKind == ListKind::ReadingList ? fromReadingListEntry : fromLabelEntry; }, + [&](const PlaceholderId &) -> QSqlQuery & { return fromPlaceholder; }, + }, + row); + query.bindValue(":parent_id", parentId); + query.bindValue(":row_id", std::visit(overloaded { + [](const ComicId &comic) { return comic.value(); }, + [](const ListItemId &entry) { return entry.id; }, + [](const PlaceholderId &placeholder) { return placeholder.value(); }, + }, + row)); + if (!query.exec()) { + QLOG_ERROR() << "Unable to append rows to" << entries.table << ":" << query.lastError().text(); + db.rollback(); + return false; + } + } + + return db.commit(); +} + +} + +std::optional DBHelper::removeLabelEntries(LabelId labelId, const QList &entryIds, QSqlDatabase &db) +{ + return removeEntries(labelEntries, labelId.value(), entryIds, db); +} + +std::optional DBHelper::removeReadingListEntries(ReadingListId readingListId, const QList &entryIds, QSqlDatabase &db) +{ + return removeEntries(readingListEntries, readingListId.value(), entryIds, db); +} + +bool DBHelper::reorderLabelEntries(LabelId labelId, const QList &entryIds, QSqlDatabase &db) +{ + return reorderEntries(labelEntries, labelId.value(), entryIds, db); +} + +bool DBHelper::reorderReadingListEntries(ReadingListId readingListId, const QList &entryIds, QSqlDatabase &db) +{ + return reorderEntries(readingListEntries, readingListId.value(), entryIds, db); +} + +bool DBHelper::appendComicsToLabel(LabelId labelId, const QList &comicInfoIds, QSqlDatabase &db) +{ + return appendComics(labelEntries, labelId.value(), comicInfoIds, db); +} + +bool DBHelper::appendComicsToReadingList(ReadingListId readingListId, const QList &comicInfoIds, QSqlDatabase &db) +{ + return appendComics(readingListEntries, readingListId.value(), comicInfoIds, db); +} + +bool DBHelper::appendRowsToLabel(LabelId labelId, const QList &rows, QSqlDatabase &db) +{ + return appendRows(labelEntries, labelId.value(), rows, db); +} + +bool DBHelper::appendRowsToReadingList(ReadingListId readingListId, const QList &rows, QSqlDatabase &db) +{ + return appendRows(readingListEntries, readingListId.value(), rows, db); } + +const QString &DBHelper::listComicsQuery(ListKind listKind) +{ + static const QString readingListQuery = ::listComicsQuery(readingListEntries); + static const QString labelQuery = ::listComicsQuery(labelEntries); + + switch (listKind) { + case ListKind::ReadingList: + return readingListQuery; + case ListKind::Label: + return labelQuery; + } + return readingListQuery; +} + +const QString &DBHelper::listEntriesQuery(ListKind listKind) +{ + static const QString readingListQuery = ::listEntriesQuery(readingListEntries); + static const QString labelQuery = ::listEntriesQuery(labelEntries); + + switch (listKind) { + case ListKind::ReadingList: + return readingListQuery; + case ListKind::Label: + return labelQuery; + } + return readingListQuery; +} + QString DBHelper::getLibraryName(int id) { return getLibraries().getName(id); @@ -256,41 +521,12 @@ QList DBHelper::getLabelComics(qulonglong libraryId, qulonglong labelId { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "INNER JOIN comic_label cl ON (c.id == cl.comic_id) " - "WHERE cl.label_id = :parentLabelId " - "ORDER BY cl.ordering"); - selectQuery.bindValue(":parentLabelId", labelId); + selectQuery.setForwardOnly(true); + selectQuery.prepare(listComicsQuery(ListKind::Label)); + selectQuery.bindValue(":list_id", labelId); selectQuery.exec(); - auto record = selectQuery.record(); - int id = record.indexOf("id"); - int fileName = record.indexOf("fileName"); - int title = record.indexOf("title"); - int currentPage = record.indexOf("currentPage"); - int numPages = record.indexOf("numPages"); - int hash = record.indexOf("hash"); - int read = record.indexOf("read"); - int coverSizeRatio = record.indexOf("coverSizeRatio"); - int number = record.indexOf("number"); - - while (selectQuery.next()) { - ComicDB comic; - - comic.id = selectQuery.value(id).toULongLong(); - comic.parentId = labelId; - comic.name = selectQuery.value(fileName).toString(); - comic.info.title = selectQuery.value(title).toString(); - comic.info.currentPage = selectQuery.value(currentPage).toInt(); - comic.info.numPages = selectQuery.value(numPages).toInt(); - comic.info.hash = selectQuery.value(hash).toString(); - comic.info.read = selectQuery.value(read).toBool(); - comic.info.coverSizeRatio = selectQuery.value(coverSizeRatio).toFloat(); - comic.info.number = selectQuery.value(number); - - list.append(comic); - } + list = comicsFromQuery(selectQuery); connectionName = db.connectionName(); } QSqlDatabase::removeDatabase(connectionName); @@ -309,41 +545,15 @@ QList DBHelper::getFavorites(qulonglong libraryId) { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) " - "WHERE cdrl.default_reading_list_id = :parentDefaultListId " - "ORDER BY cdrl.ordering"); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " " + "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " + "INNER JOIN comic_default_reading_list cdrl ON (c.id == cdrl.comic_id) " + "WHERE cdrl.default_reading_list_id = :parentDefaultListId " + "ORDER BY cdrl.ordering"); selectQuery.bindValue(":parentDefaultListId", FAV_ID); selectQuery.exec(); - auto record = selectQuery.record(); - int id = record.indexOf("id"); - int fileName = record.indexOf("fileName"); - int title = record.indexOf("title"); - int currentPage = record.indexOf("currentPage"); - int numPages = record.indexOf("numPages"); - int hash = record.indexOf("hash"); - int read = record.indexOf("read"); - int coverSizeRatio = record.indexOf("coverSizeRatio"); - int number = record.indexOf("number"); - - while (selectQuery.next()) { - ComicDB comic; - - comic.id = selectQuery.value(id).toULongLong(); - comic.parentId = FAV_ID; - comic.name = selectQuery.value(fileName).toString(); - comic.info.title = selectQuery.value(title).toString(); - comic.info.currentPage = selectQuery.value(currentPage).toInt(); - comic.info.numPages = selectQuery.value(numPages).toInt(); - comic.info.hash = selectQuery.value(hash).toString(); - comic.info.read = selectQuery.value(read).toBool(); - comic.info.coverSizeRatio = selectQuery.value(coverSizeRatio).toFloat(); - comic.info.number = selectQuery.value(number); - - list.append(comic); - } + list = comicsFromQuery(selectQuery); connectionName = db.connectionName(); } @@ -362,41 +572,13 @@ QList DBHelper::getReading(qulonglong libraryId) { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); QSqlQuery selectQuery(db); - selectQuery.prepare("SELECT c.id,c.parentId,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "WHERE ci.hasBeenOpened = 1 AND ci.read = 0 " - "ORDER BY ci.lastTimeOpened DESC"); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " " + "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " + "WHERE ci.hasBeenOpened = 1 AND ci.read = 0 " + "ORDER BY ci.lastTimeOpened DESC"); selectQuery.exec(); - auto record = selectQuery.record(); - int id = record.indexOf("id"); - int parentId = record.indexOf("parentId"); - int fileName = record.indexOf("fileName"); - int title = record.indexOf("title"); - int currentPage = record.indexOf("currentPage"); - int numPages = record.indexOf("numPages"); - int hash = record.indexOf("hash"); - int read = record.indexOf("read"); - int coverSizeRatio = record.indexOf("coverSizeRatio"); - int number = record.indexOf("number"); - - while (selectQuery.next()) { - ComicDB comic; - - // TODO: use QVariant when possible to keep nulls - comic.id = selectQuery.value(id).toULongLong(); - comic.parentId = selectQuery.value(parentId).toULongLong(); - comic.name = selectQuery.value(fileName).toString(); - comic.info.title = selectQuery.value(title).toString(); - comic.info.currentPage = selectQuery.value(currentPage).toInt(); - comic.info.numPages = selectQuery.value(numPages).toInt(); - comic.info.hash = selectQuery.value(hash).toString(); - comic.info.read = selectQuery.value(read).toBool(); - comic.info.coverSizeRatio = selectQuery.value(coverSizeRatio).toFloat(); - comic.info.number = selectQuery.value(number); - - list.append(comic); - } + list = comicsFromQuery(selectQuery); connectionName = db.connectionName(); } // TODO ? @@ -414,24 +596,21 @@ QList DBHelper::getReadingLists(qulonglong libraryId) { QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - QSqlQuery selectQuery("SELECT * from reading_list WHERE parentId IS NULL ORDER BY name DESC", db); + QSqlQuery selectQuery("SELECT " + TableProjection::selectList("rl", ReadingList::columns()) + + " FROM reading_list rl WHERE rl.parentId IS NULL ORDER BY rl.name DESC", + db); selectQuery.exec(); - QSqlRecord record = selectQuery.record(); - - int name = record.indexOf("name"); - int id = record.indexOf("id"); - int ordering = record.indexOf("ordering"); - + const auto indexes = ReadingList::columnIndexes(selectQuery.record(), "rl"); while (selectQuery.next()) { - ReadingList item(selectQuery.value(name).toString(), selectQuery.value(id).toLongLong(), selectQuery.value(ordering).toInt()); + const auto item = ReadingList::fromQuery(selectQuery, indexes); if (list.isEmpty()) { list.append(item); } else { int i = 0; - while (i < list.length() && naturalSortLessThanCI(list.at(i).getName(), item.getName())) + while (i < list.length() && naturalSortLessThanCI(list.at(i).name, item.name)) i++; list.insert(i, item); } @@ -444,7 +623,7 @@ QList DBHelper::getReadingLists(qulonglong libraryId) return list; } -QList DBHelper::getReadingListFullContent(qulonglong libraryId, qulonglong readingListId, bool getFullComicInfoFields) +QList DBHelper::getReadingListFullContent(qulonglong libraryId, qulonglong readingListId, QList *entryIds) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QList list; @@ -465,65 +644,22 @@ QList DBHelper::getReadingListFullContent(qulonglong libraryId, qulongl while (subfolders.next()) ids << subfolders.value(0).toULongLong(); - const auto &subfolderIds = ids; - for (const auto id : subfolderIds) { - QSqlQuery selectQuery(db); - - QString params; - if (getFullComicInfoFields) { - params = "*"; - } else { - params = "c.id,c.parentId,c.fileName,c.path,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number"; - } - - selectQuery.prepare("SELECT " + params + " " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) " - "INNER JOIN comic_reading_list crl ON (c.id == crl.comic_id) " - "WHERE crl.reading_list_id = :parentReadingList " - "ORDER BY crl.ordering"); - selectQuery.bindValue(":parentReadingList", id); + QSqlQuery selectQuery(db); + selectQuery.setForwardOnly(true); + selectQuery.prepare(listComicsQuery(ListKind::ReadingList)); + for (const auto id : std::as_const(ids)) { + selectQuery.bindValue(":list_id", id); selectQuery.exec(); - auto record = selectQuery.record(); - - int idComicIndex = record.indexOf("id"); - int parentIdIndex = record.indexOf("parentId"); - int fileName = record.indexOf("fileName"); - int path = record.indexOf("path"); - int title = record.indexOf("title"); - int currentPage = record.indexOf("currentPage"); - int numPages = record.indexOf("numPages"); - int hash = record.indexOf("hash"); - int read = record.indexOf("read"); - int coverSizeRatio = record.indexOf("coverSizeRatio"); - int number = record.indexOf("number"); - + if (entryIds == nullptr) { + list.append(comicsFromQuery(selectQuery)); + continue; + } + const auto comicIndexes = ComicDB::columnIndexes(selectQuery.record(), "c", "ci"); + const auto entryIndexes = ListEntry::columnIndexes(selectQuery.record(), "e", ListKind::ReadingList); while (selectQuery.next()) { - ComicDB comic; - - if (getFullComicInfoFields) { - comic.id = selectQuery.value(idComicIndex).toULongLong(); - comic.parentId = selectQuery.value(parentIdIndex).toULongLong(); - comic.name = selectQuery.value(fileName).toString(); - comic.path = selectQuery.value(path).toString(); - - comic.info = getComicInfoFromQuery(selectQuery, "comicInfoId"); - } else { - comic.id = selectQuery.value(idComicIndex).toULongLong(); - comic.parentId = selectQuery.value(parentIdIndex).toULongLong(); - comic.name = selectQuery.value(fileName).toString(); - comic.path = selectQuery.value(path).toString(); - - comic.info.title = selectQuery.value(title).toString(); - comic.info.currentPage = selectQuery.value(currentPage).toInt(); - comic.info.numPages = selectQuery.value(numPages).toInt(); - comic.info.hash = selectQuery.value(hash).toString(); - comic.info.read = selectQuery.value(read).toBool(); - comic.info.coverSizeRatio = selectQuery.value(coverSizeRatio).toFloat(); - comic.info.number = selectQuery.value(number); - } - - list.append(comic); + list.append(ComicDB::fromQuery(selectQuery, comicIndexes)); + entryIds->append(ListEntry::fromQuery(selectQuery, entryIndexes, ListKind::ReadingList).id); } } connectionName = db.connectionName(); @@ -537,108 +673,350 @@ QList DBHelper::getReadingListFullContent(qulonglong libraryId, qulongl // objects management // deletes -void DBHelper::removeFromDB(LibraryItem *item, QSqlDatabase &db) +void DBHelper::removeFromDB(DiskItem &item, QSqlDatabase &db) { - if (item->isDir()) - DBHelper::removeFromDB(dynamic_cast(item), db); - else - DBHelper::removeFromDB(dynamic_cast(item), db); + std::visit(overloaded { + [&db](Folder &folder) { DBHelper::removeFromDB(&folder, db); }, + [&db](ComicDB &comic) { DBHelper::removeFromDB(&comic, db); }, + }, + item); } void DBHelper::removeFromDB(Folder *folder, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("DELETE FROM folder WHERE id = :id"); - query.bindValue(":id", folder->id); + query.bindValue(":id", folder->id.value()); query.exec(); } void DBHelper::removeFromDB(ComicDB *comic, QSqlDatabase &db) { QSqlQuery query(db); query.prepare("DELETE FROM comic WHERE id = :id"); - query.bindValue(":id", comic->id); + query.bindValue(":id", comic->file.id.value()); query.exec(); } -void DBHelper::removeLabelFromDB(qulonglong id, QSqlDatabase &db) +namespace { + +// the tables of one kind of list folders +struct FolderTable { + const char *table; + const char *listTable; +}; + +FolderTable folderTable(ListFolderKind kind) { - QSqlQuery query(db); - query.prepare("DELETE FROM label WHERE id = :id"); - query.bindValue(":id", id); - query.exec(); + switch (kind) { + case ListFolderKind::ReadingList: + return { "reading_list_folder", "reading_list" }; + case ListFolderKind::SmartList: + return { "smart_list_folder", "smart_list" }; + } + return { "reading_list_folder", "reading_list" }; } -void DBHelper::removeListFromDB(qulonglong id, QSqlDatabase &db) +bool rowExists(const QString &table, qulonglong id, QSqlDatabase &db) { QSqlQuery query(db); - query.prepare("DELETE FROM reading_list WHERE id = :id"); + query.prepare(QString("SELECT 1 FROM %1 WHERE id = :id").arg(table)); query.bindValue(":id", id); - query.exec(); + return query.exec() && query.next(); } -void DBHelper::deleteComicsFromFavorites(const QList &comicsList, QSqlDatabase &db) +// a top level reading list or a smart list goes to the end of `folderId` (nothing: the top level) +bool moveListToFolder(ListFolderKind kind, qulonglong listId, const std::optional &folderId, QSqlDatabase &db) { - db.transaction(); + const auto tables = folderTable(kind); + const QString topLevelOnly = kind == ListFolderKind::ReadingList ? " AND parentId IS NULL" : ""; + const QVariant folder = folderId ? QVariant(*folderId) : QVariant(); - QLOG_DEBUG() << "deleteComicsFromFavorites----------------------------------"; + if (!beginTransaction(db)) + return false; + QSqlQuery list(db); + list.prepare(QString("SELECT 1 FROM %1 WHERE id = :id%2").arg(tables.listTable, topLevelOnly)); + list.bindValue(":id", listId); + bool moved = list.exec() && list.next() && (!folderId || rowExists(tables.table, *folderId, db)); + list.finish(); + + if (moved) { + QSqlQuery move(db); + move.prepare(QString("UPDATE %1 SET folder_id = :folder_id, " + "ordering = (SELECT COALESCE(MAX(ordering) + 1, 0) FROM %1 WHERE folder_id IS :folder_id AND id <> :id%2) " + "WHERE id = :id") + .arg(tables.listTable, topLevelOnly)); + move.bindValue(":folder_id", folder); + move.bindValue(":id", listId); + moved = move.exec() && move.numRowsAffected() == 1; + if (!moved) + QLOG_ERROR() << "Unable to move a list to a folder:" << move.lastError().text(); + } + + if (moved && db.commit()) + return true; + db.rollback(); + return false; +} + +} + +std::optional DBHelper::removeLabelFromDB(qulonglong id, QSqlDatabase &db) +{ + QSqlQuery placeholderIds(db); + placeholderIds.prepare("SELECT DISTINCT placeholder_id FROM label_entry WHERE placeholder_id IS NOT NULL AND label_id = :id"); + placeholderIds.bindValue(":id", id); + const auto removal = [&] { + QSqlQuery query(db); + query.prepare("DELETE FROM label WHERE id = :id"); + query.bindValue(":id", id); + if (!query.exec()) { + QLOG_ERROR() << "Unable to remove a label:" << query.lastError().text(); + return false; + } + return true; + }; + return removeWithPlaceholders(placeholderIds, removal, db); +} + +std::optional DBHelper::removeListFromDB(qulonglong id, QSqlDatabase &db) +{ + QSqlQuery placeholderIds(db); + placeholderIds.prepare("WITH RECURSIVE lists(id) AS (SELECT :id UNION ALL SELECT r.id FROM reading_list r INNER JOIN lists l ON r.parentId = l.id) " + "SELECT DISTINCT placeholder_id FROM reading_list_entry WHERE placeholder_id IS NOT NULL AND reading_list_id IN (SELECT id FROM lists)"); + placeholderIds.bindValue(":id", id); + + const auto removal = [&] { + QSqlQuery query(db); + query.prepare("DELETE FROM reading_list WHERE id = :id"); + query.bindValue(":id", id); + if (!query.exec()) { + QLOG_ERROR() << "Unable to remove a reading list:" << query.lastError().text(); + return false; + } + return true; + }; + return removeWithPlaceholders(placeholderIds, removal, db); +} + +void DBHelper::removePlaceholderCovers(const QString &libraryDataPath, const QStringList &coverKeys) +{ + for (const auto &coverKey : coverKeys) { + QFile::remove(LibraryPaths::placeholderCoverPathFromDataPath(libraryDataPath, coverKey)); + } +} + +std::optional DBHelper::removeUnusedPlaceholders(QSqlDatabase &db) +{ + QSqlQuery select(db); + select.setForwardOnly(true); + if (!select.exec("SELECT p.coverKey FROM placeholder p WHERE p.coverKey IS NOT NULL AND " + placeholderUnused())) { + QLOG_ERROR() << "Unable to find unused placeholders:" << select.lastError().text(); + return std::nullopt; + } + QStringList coverKeys; + while (select.next()) { + coverKeys.append(select.value(0).toString()); + } + select.finish(); + + QSqlQuery remove(db); + if (!remove.exec("DELETE FROM placeholder WHERE id IN (SELECT p.id FROM placeholder p WHERE " + placeholderUnused() + ")")) { + QLOG_ERROR() << "Unable to delete unused placeholders:" << remove.lastError().text(); + return std::nullopt; + } + return coverKeys; +} + +bool DBHelper::placePlaceholderInFolder(PlaceholderId placeholderId, FolderId folderId, QSqlDatabase &db) +{ QSqlQuery query(db); - query.prepare("DELETE FROM comic_default_reading_list WHERE comic_id = :comic_id AND default_reading_list_id = 1"); - for (const auto &comic : comicsList) { - query.bindValue(":comic_id", comic.id); - query.exec(); + query.prepare("INSERT OR IGNORE INTO folder_placeholder (folder_id, placeholder_id) VALUES (:folder_id, :placeholder_id)"); + query.bindValue(":folder_id", folderId.value()); + query.bindValue(":placeholder_id", placeholderId.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to place a placeholder in a folder:" << query.lastError().text(); + return false; } + return true; +} - db.commit(); +bool DBHelper::insertPlaceholdersInFolder(const QList &placeholders, FolderId folderId, QSqlDatabase &db) +{ + QSqlQuery insert(db); + insert.prepare("INSERT INTO placeholder (" + TableProjection::insertColumns(Placeholder::columns()) + ") VALUES (" + TableProjection::insertValues(Placeholder::columns()) + ")"); + QSqlQuery place(db); + place.prepare("INSERT INTO folder_placeholder (folder_id, placeholder_id) VALUES (:folder_id, :placeholder_id)"); + + for (const auto &placeholder : placeholders) { + placeholder.bindTo(insert); + if (!insert.exec()) { + QLOG_ERROR() << "Unable to insert a placeholder:" << insert.lastError().text(); + return false; + } + place.bindValue(":folder_id", folderId.value()); + place.bindValue(":placeholder_id", insert.lastInsertId()); + if (!place.exec()) { + QLOG_ERROR() << "Unable to place a placeholder in a folder:" << place.lastError().text(); + return false; + } + } + return true; } -// a.k.a set comics as unread by reverting the conditions used to load the comics -> void ComicModel::setupReadingModelData(const QString &databasePath) -void DBHelper::deleteComicsFromReading(const QList &comicsList, QSqlDatabase &db) +bool DBHelper::insertPlaceholdersInReadingList(const QList &placeholders, ReadingListId readingListId, QSqlDatabase &db) { - db.transaction(); + QSqlQuery insert(db); + insert.prepare("INSERT INTO placeholder (" + TableProjection::insertColumns(Placeholder::columns()) + ") VALUES (" + TableProjection::insertValues(Placeholder::columns()) + ")"); + QSqlQuery append(db); + append.prepare("INSERT INTO reading_list_entry (reading_list_id, ordering, placeholder_id) " + "SELECT :reading_list_id, COALESCE(MAX(ordering), -1) + 1, :placeholder_id FROM reading_list_entry WHERE reading_list_id = :reading_list_id"); - QLOG_DEBUG() << "deleteComicsFromReading----------------------------------"; + for (const auto &placeholder : placeholders) { + placeholder.bindTo(insert); + if (!insert.exec()) { + QLOG_ERROR() << "Unable to insert a placeholder:" << insert.lastError().text(); + return false; + } + append.bindValue(":reading_list_id", readingListId.value()); + append.bindValue(":placeholder_id", insert.lastInsertId()); + if (!append.exec()) { + QLOG_ERROR() << "Unable to append a placeholder to a reading list:" << append.lastError().text(); + return false; + } + } + return true; +} - for (auto comic : comicsList) { - comic.info.hasBeenOpened = false; - comic.info.currentPage = 0; // update sets hasBeenOpened to true if currentPage > 0; - DBHelper::update(&comic.info, db); +std::optional DBHelper::placeIssuesInFolder(const QList &issues, FolderId folderId, QSqlDatabase &db) +{ + QStringList unusedCovers; + if (issues.isEmpty()) + return unusedCovers; + + QStringList parameters; + for (qsizetype i = 0; i < issues.size(); i++) { + if (issues.at(i).comicVineId.trimmed().isEmpty()) + return std::nullopt; + parameters.append(QString(":id%1").arg(i)); } - db.commit(); + QSqlQuery inFolder(db); + inFolder.setForwardOnly(true); + inFolder.prepare("SELECT ci.comicVineID FROM comic c INNER JOIN comic_info ci ON ci.id = c.comicInfoId WHERE c.parentId = :folder_id AND ci.comicVineID IS NOT NULL"); + inFolder.bindValue(":folder_id", folderId.value()); + if (!inFolder.exec()) { + QLOG_ERROR() << "Unable to find the Comic Vine ids of the comics of a folder:" << inFolder.lastError().text(); + return std::nullopt; + } + QSet comicsInFolder; + while (inFolder.next()) + comicsInFolder.insert(inFolder.value(0).toString()); + + // the placeholder of each issue is the one with the lowest id + QSqlQuery existing(db); + existing.setForwardOnly(true); + existing.prepare("SELECT comicVineID, id, coverKey FROM placeholder WHERE comicVineID IN (" + parameters.join(", ") + ") ORDER BY id"); + for (qsizetype i = 0; i < issues.size(); i++) + existing.bindValue(parameters.at(i), issues.at(i).comicVineId); + if (!existing.exec()) { + QLOG_ERROR() << "Unable to find the placeholders of the issues:" << existing.lastError().text(); + return std::nullopt; + } + struct Found { + PlaceholderId id; + bool hasCover; + }; + QHash placeholders; + while (existing.next()) { + const auto comicVineId = existing.value(0).toString(); + if (!placeholders.contains(comicVineId)) + placeholders.insert(comicVineId, { PlaceholderId(existing.value(1).toULongLong()), !existing.value(2).isNull() }); + } + + for (const auto &issue : issues) { + if (comicsInFolder.contains(issue.comicVineId)) { + if (issue.coverKey) + unusedCovers.append(*issue.coverKey); + continue; + } + + auto found = placeholders.find(issue.comicVineId); + if (found == placeholders.end()) { + const auto inserted = insertPlaceholder(Placeholder(), db); + if (!inserted) + return std::nullopt; + found = placeholders.insert(issue.comicVineId, { *inserted, false }); + } + + if (!updatePlaceholderMetadata(found->id, issue.patch, db)) + return std::nullopt; + if (issue.coverKey && !found->hasCover) { + if (!updatePlaceholderCover(found->id, issue.coverKey, db)) + return std::nullopt; + found->hasCover = true; + } else if (issue.coverKey) { + unusedCovers.append(*issue.coverKey); + } + if (!placePlaceholderInFolder(found->id, folderId, db)) + return std::nullopt; + } + return unusedCovers; +} + +std::optional DBHelper::removePlaceholdersFromFolder(const QList &placeholderIds, FolderId folderId, QSqlDatabase &db) +{ + QStringList ids; + for (const auto &placeholderId : placeholderIds) { + ids.append(QString::number(placeholderId.value())); + } + + QSqlQuery placed(db); + placed.prepare(QString("SELECT placeholder_id FROM folder_placeholder WHERE folder_id = :folder_id AND placeholder_id IN (%1)").arg(ids.join(","))); + placed.bindValue(":folder_id", folderId.value()); + + const auto removal = [&] { + QSqlQuery query(db); + query.prepare("DELETE FROM folder_placeholder WHERE folder_id = :folder_id AND placeholder_id = :placeholder_id"); + for (const auto &placeholderId : placeholderIds) { + query.bindValue(":folder_id", folderId.value()); + query.bindValue(":placeholder_id", placeholderId.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to remove placeholders from a folder:" << query.lastError().text(); + return false; + } + } + return true; + }; + return removeWithPlaceholders(placed, removal, db); } -void DBHelper::deleteComicsFromLabel(const QList &comicsList, qulonglong labelId, QSqlDatabase &db) +void DBHelper::deleteComicsFromFavorites(const QList &comicsList, QSqlDatabase &db) { db.transaction(); - QLOG_DEBUG() << "deleteComicsFromLabel----------------------------------"; + QLOG_DEBUG() << "deleteComicsFromFavorites----------------------------------"; QSqlQuery query(db); - query.prepare("DELETE FROM comic_label WHERE comic_id = :comic_id AND label_id = :label_id"); + query.prepare("DELETE FROM comic_default_reading_list WHERE comic_id = :comic_id AND default_reading_list_id = 1"); for (const auto &comic : comicsList) { - query.bindValue(":comic_id", comic.id); - query.bindValue(":label_id", labelId); + query.bindValue(":comic_id", comic.file.id.value()); query.exec(); - - QLOG_DEBUG() << "cid = " << comic.id << "lid = " << labelId; - QLOG_DEBUG() << query.lastError().databaseText() << "-" << query.lastError().driverText(); } db.commit(); } -void DBHelper::deleteComicsFromReadingList(const QList &comicsList, qulonglong readingListId, QSqlDatabase &db) +// a.k.a set comics as unread by reverting the conditions used to load the comics -> void ComicModel::setupReadingModelData(const QString &databasePath) +void DBHelper::deleteComicsFromReading(const QList &comicsList, QSqlDatabase &db) { db.transaction(); - QLOG_DEBUG() << "deleteComicsFromReadingList----------------------------------"; + QLOG_DEBUG() << "deleteComicsFromReading----------------------------------"; - QSqlQuery query(db); - query.prepare("DELETE FROM comic_reading_list WHERE comic_id = :comic_id AND reading_list_id = :reading_list_id"); - for (const auto &comic : comicsList) { - query.bindValue(":comic_id", comic.id); - query.bindValue(":reading_list_id", readingListId); - query.exec(); + for (auto comic : comicsList) { + comic.info.hasBeenOpened = false; + comic.info.currentPage = 0; // update sets hasBeenOpened to true if currentPage > 0; + DBHelper::update(&comic.info, db); } db.commit(); @@ -664,46 +1042,16 @@ void DBHelper::update(qulonglong libraryId, ComicInfo &comicInfo) QSqlDatabase::removeDatabase(connectionName); } -void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) +bool DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) { if (comicInfo == nullptr) - return; + return false; QSqlQuery updateComicInfo(db); - updateComicInfo.prepare("UPDATE comic_info SET " - "title = :title," - + updateComicInfo.prepare("UPDATE comic_info SET " + DescriptiveComicMetadata::assignments().join(",") + "," + "coverPage = :coverPage," "numPages = :numPages," - "number = :number," - "isBis = :isBis," - "count = :count," - - "volume = :volume," - "storyArc = :storyArc," - "arcNumber = :arcNumber," - "arcCount = :arcCount," - - "genere = :genere," - - "writer = :writer," - "penciller = :penciller," - "inker = :inker," - "colorist = :colorist," - "letterer = :letterer," - "coverArtist = :coverArtist," - - "date = :date," - "publisher = :publisher," - "format = :format," - "color = :color," - "ageRating = :ageRating," - - "synopsis = :synopsis," - "characters = :characters," - "notes = :notes," - "read = :read," "edited = :edited," // new 7.0 fields @@ -718,9 +1066,6 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) "gamma = :gamma," "rating = :rating," - // new 7.1 fields - "comicVineID = :comicVineID," - // new 9.5 fields "lastTimeOpened = :lastTimeOpened," @@ -735,19 +1080,6 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) // new 9.13 fields "added = :added," "type = :type," - "editor = :editor," - "imprint = :imprint," - "teams = :teams," - "locations = :locations," - "series = :series," - "alternateSeries = :alternateSeries," - "alternateNumber = :alternateNumber," - "alternateCount = :alternateCount," - "languageISO = :languageISO," - "seriesGroup = :seriesGroup," - "mainCharacterOrTeam = :mainCharacterOrTeam," - "review = :review," - "tags = :tags," // new 9.16 fields "imageFiltersJson = :imageFiltersJson," @@ -759,43 +1091,15 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) //-- " WHERE id = :id"); - updateComicInfo.bindValue(":title", comicInfo->title); + comicInfo->bindTo(updateComicInfo); updateComicInfo.bindValue(":coverPage", comicInfo->coverPage); updateComicInfo.bindValue(":numPages", comicInfo->numPages); - updateComicInfo.bindValue(":number", comicInfo->number); - updateComicInfo.bindValue(":isBis", comicInfo->isBis); - updateComicInfo.bindValue(":count", comicInfo->count); - - updateComicInfo.bindValue(":volume", comicInfo->volume); - updateComicInfo.bindValue(":storyArc", comicInfo->storyArc); - updateComicInfo.bindValue(":arcNumber", comicInfo->arcNumber); - updateComicInfo.bindValue(":arcCount", comicInfo->arcCount); - - updateComicInfo.bindValue(":genere", comicInfo->genere); - - updateComicInfo.bindValue(":writer", comicInfo->writer); - updateComicInfo.bindValue(":penciller", comicInfo->penciller); - updateComicInfo.bindValue(":inker", comicInfo->inker); - updateComicInfo.bindValue(":colorist", comicInfo->colorist); - updateComicInfo.bindValue(":letterer", comicInfo->letterer); - updateComicInfo.bindValue(":coverArtist", comicInfo->coverArtist); - - updateComicInfo.bindValue(":date", comicInfo->date); - updateComicInfo.bindValue(":publisher", comicInfo->publisher); - updateComicInfo.bindValue(":format", comicInfo->format); - updateComicInfo.bindValue(":color", comicInfo->color); - updateComicInfo.bindValue(":ageRating", comicInfo->ageRating); - - updateComicInfo.bindValue(":synopsis", comicInfo->synopsis); - updateComicInfo.bindValue(":characters", comicInfo->characters); - updateComicInfo.bindValue(":notes", comicInfo->notes); - bool read = comicInfo->read || comicInfo->currentPage == comicInfo->numPages.toInt(); // if current page is the las page, the comic is read(completed) comicInfo->read = read; updateComicInfo.bindValue(":read", read ? 1 : 0); - updateComicInfo.bindValue(":id", comicInfo->id); + updateComicInfo.bindValue(":id", comicInfo->id.value()); updateComicInfo.bindValue(":edited", comicInfo->edited ? 1 : 0); updateComicInfo.bindValue(":hasBeenOpened", comicInfo->hasBeenOpened ? 1 : 0 || comicInfo->currentPage > 1); @@ -808,8 +1112,6 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) updateComicInfo.bindValue(":gamma", comicInfo->gamma); updateComicInfo.bindValue(":rating", comicInfo->rating); - updateComicInfo.bindValue(":comicVineID", comicInfo->comicVineID); - updateComicInfo.bindValue(":lastTimeOpened", comicInfo->lastTimeOpened); updateComicInfo.bindValue(":coverSizeRatio", comicInfo->coverSizeRatio); @@ -818,19 +1120,6 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) updateComicInfo.bindValue(":added", comicInfo->added); auto intType = static_cast(comicInfo->type.value()); updateComicInfo.bindValue(":type", intType); - updateComicInfo.bindValue(":editor", comicInfo->editor); - updateComicInfo.bindValue(":imprint", comicInfo->imprint); - updateComicInfo.bindValue(":teams", comicInfo->teams); - updateComicInfo.bindValue(":locations", comicInfo->locations); - updateComicInfo.bindValue(":series", comicInfo->series); - updateComicInfo.bindValue(":alternateSeries", comicInfo->alternateSeries); - updateComicInfo.bindValue(":alternateNumber", comicInfo->alternateNumber); - updateComicInfo.bindValue(":alternateCount", comicInfo->alternateCount); - updateComicInfo.bindValue(":languageISO", comicInfo->languageISO); - updateComicInfo.bindValue(":seriesGroup", comicInfo->seriesGroup); - updateComicInfo.bindValue(":mainCharacterOrTeam", comicInfo->mainCharacterOrTeam); - updateComicInfo.bindValue(":review", comicInfo->review); - updateComicInfo.bindValue(":tags", comicInfo->tags); updateComicInfo.bindValue(":imageFiltersJson", comicInfo->imageFiltersJson); updateComicInfo.bindValue(":lastTimeImageFiltersSet", comicInfo->lastTimeImageFiltersSet); @@ -838,26 +1127,92 @@ void DBHelper::update(ComicInfo *comicInfo, QSqlDatabase &db) updateComicInfo.bindValue(":usesExternalCover", comicInfo->usesExternalCover); updateComicInfo.bindValue(":lastTimeMetadataSet", comicInfo->lastTimeMetadataSet); - updateComicInfo.exec(); + const bool success = updateComicInfo.exec(); QLOG_INFO() << updateComicInfo.lastError().databaseText(); QLOG_INFO() << updateComicInfo.lastError().text(); QLOG_INFO() << updateComicInfo.lastQuery(); + + return success; } -void DBHelper::updateRead(ComicInfo *comicInfo, QSqlDatabase &db) +bool DBHelper::updateComicInfoMetadata(ComicInfoId id, const DescriptiveMetadataPatch &patch, QSqlDatabase &db) { - QSqlQuery updateComicInfo(db); - updateComicInfo.prepare("UPDATE comic_info SET " - "read = :read" - " WHERE id = :id "); + if (patch.isEmpty()) + return true; - updateComicInfo.bindValue(":read", comicInfo->read ? 1 : 0); - updateComicInfo.bindValue(":id", comicInfo->id); - updateComicInfo.exec(); + QSqlQuery query(db); + query.prepare("UPDATE comic_info SET " + patch.assignments().join(", ") + ", edited = 1 WHERE id = :id"); + patch.bindTo(query); + query.bindValue(":id", id.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to update the metadata of a comic:" << query.lastError().text(); + return false; + } + return true; +} + +bool DBHelper::updatePlaceholderMetadata(PlaceholderId id, const DescriptiveMetadataPatch &patch, QSqlDatabase &db) +{ + if (patch.isEmpty()) + return true; + + QSqlQuery query(db); + query.prepare("UPDATE placeholder SET " + patch.assignments().join(", ") + " WHERE id = :id"); + patch.bindTo(query); + query.bindValue(":id", id.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to update the metadata of a placeholder:" << query.lastError().text(); + return false; + } + return true; +} + +bool DBHelper::updatePlaceholderCover(PlaceholderId id, const std::optional &coverKey, QSqlDatabase &db) +{ + QSqlQuery query(db); + query.prepare("UPDATE placeholder SET coverKey = :coverKey WHERE id = :id"); + query.bindValue(":coverKey", coverKey ? QVariant(*coverKey) : QVariant()); + query.bindValue(":id", id.value()); + if (!query.exec() || query.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to update the cover of a placeholder:" << query.lastError().text(); + return false; + } + return true; +} + +bool DBHelper::updateComicInfoType(ComicInfoId id, YACReader::FileType type, QSqlDatabase &db) +{ + QSqlQuery query(db); + query.prepare("UPDATE comic_info SET type = :type, edited = 1 WHERE id = :id"); + query.bindValue(":type", static_cast(type)); + query.bindValue(":id", id.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to update the type of a comic:" << query.lastError().text(); + return false; + } + return true; +} + +bool DBHelper::updateComicInfoCover(const ComicInfo &info, QSqlDatabase &db) +{ + QSqlQuery query(db); + query.prepare("UPDATE comic_info SET coverPage = :coverPage, originalCoverSize = :originalCoverSize, coverSizeRatio = :coverSizeRatio, " + "lastTimeCoverSet = :lastTimeCoverSet, usesExternalCover = :usesExternalCover, edited = 1 WHERE id = :id"); + query.bindValue(":coverPage", info.coverPage); + query.bindValue(":originalCoverSize", info.originalCoverSize); + query.bindValue(":coverSizeRatio", info.coverSizeRatio); + query.bindValue(":lastTimeCoverSet", info.lastTimeCoverSet); + query.bindValue(":usesExternalCover", info.usesExternalCover); + query.bindValue(":id", info.id.value()); + if (!query.exec()) { + QLOG_ERROR() << "Unable to update the cover of a comic:" << query.lastError().text(); + return false; + } + return true; } -void DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db) +bool DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db) { QSqlQuery updateComicInfo(db); updateComicInfo.prepare("UPDATE comic_info SET " @@ -865,79 +1220,56 @@ void DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db) " WHERE id = :id "); updateComicInfo.bindValue(":added", comicInfo->added); - updateComicInfo.bindValue(":id", comicInfo->id); - updateComicInfo.exec(); + updateComicInfo.bindValue(":id", comicInfo->id.value()); + return updateComicInfo.exec(); } void DBHelper::update(const Folder &folder, QSqlDatabase &db) { QSqlQuery updateFolderInfo(db); - updateFolderInfo.prepare("UPDATE folder SET " - "parentId = :parentId, " - "name = :name, " - "path = :path, " - "finished = :finished, " - "completed = :completed, " - "numChildren = :numChildren, " - "firstChildHash = :firstChildHash, " - "customImage = :customImage, " - "type = :type, " - "added = :added, " - "updated = :updated " - "WHERE id = :id"); + updateFolderInfo.prepare("UPDATE folder SET " + TableProjection::assignments(Folder::columns()) + " WHERE id = :id"); - updateFolderInfo.bindValue(":parentId", folder.parentId); - updateFolderInfo.bindValue(":name", folder.name); - updateFolderInfo.bindValue(":path", folder.path); - updateFolderInfo.bindValue(":finished", folder.finished ? 1 : 0); - updateFolderInfo.bindValue(":completed", folder.completed ? 1 : 0); - updateFolderInfo.bindValue(":numChildren", folder.numChildren); - updateFolderInfo.bindValue(":firstChildHash", folder.firstChildHash); - updateFolderInfo.bindValue(":customImage", folder.customImage); - updateFolderInfo.bindValue(":type", static_cast(folder.type)); - updateFolderInfo.bindValue(":added", folder.added); - updateFolderInfo.bindValue(":updated", folder.updated); - updateFolderInfo.bindValue(":id", folder.id); + folder.bindTo(updateFolderInfo); + updateFolderInfo.bindValue(":id", folder.id.value()); updateFolderInfo.exec(); } void DBHelper::propagateFolderUpdatesToParent(const Folder &folder, QSqlDatabase &db) { - auto currentParentId = folder.parentId; - auto currentId = folder.id; + auto currentParentId = folder.parentId.value(); + auto currentId = folder.id.value(); while (currentParentId != 1 && currentParentId != 0) { // currentParentId is 0 if `folder` is the root folder - auto f = loadFolder(currentParentId, db); - currentParentId = f.parentId; - currentId = f.id; + const auto f = loadFolder(currentParentId, db); + currentId = currentParentId; + currentParentId = f ? f->parentId.value() : 0; } - if (currentId != folder.id) { + if (currentId != folder.id.value()) { updateChildrenInfo(currentId, db); } } Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) { - auto folder = loadFolder(folderId, db); - QList subfolders = DBHelper::getFoldersFromParent(folderId, db, true); - QList comics = DBHelper::getComicsFromParent(folderId, db, true); + auto folder = loadFolder(folderId, db).value_or(Folder()); + folder.id = FolderId(folderId); + const auto subfolders = DBHelper::getFoldersFromParent(folderId, db, true); + const auto comics = DBHelper::getComicsFromParent(folderId, db, true); - QList updatedSubfolders; - for (auto sf : subfolders) { - updatedSubfolders.append(new Folder(updateChildrenInfo(static_cast(sf)->id, db))); + QList updatedSubfolders; + for (const auto &sf : subfolders) { + updatedSubfolders.append(updateChildrenInfo(sf.id.value(), db)); } QString coverHash = ""; if (!comics.isEmpty()) { - auto c = static_cast(comics[0]); - coverHash = c->info.hash; + coverHash = comics.first().info.hash; } else { - for (auto item : updatedSubfolders) { - auto f = static_cast(item); - auto firstChildHash = f->firstChildHash; + for (const auto &f : updatedSubfolders) { + auto firstChildHash = f.firstChildHash; if (!firstChildHash.isEmpty()) { coverHash = firstChildHash; break; @@ -962,10 +1294,6 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db) updateFolderInfo.bindValue(":id", folderId); updateFolderInfo.exec(); - qDeleteAll(subfolders); - qDeleteAll(updatedSubfolders); - qDeleteAll(comics); - return folder; } @@ -989,7 +1317,7 @@ void DBHelper::updateChildrenInfo(QSqlDatabase &db) QLOG_INFO() << message; } -void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) +void DBHelper::updateProgress(qulonglong libraryId, ComicId comicId, const ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; @@ -997,7 +1325,7 @@ void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; - ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); + ComicDB comic = DBHelper::loadComic(comicId.value(), db, found); comic.info.currentPage = comicInfo.currentPage; comic.info.hasBeenOpened = comicInfo.currentPage > 0 || comic.info.hasBeenOpened; comic.info.read = comic.info.read || comic.info.currentPage == comic.info.numPages; @@ -1010,7 +1338,7 @@ void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo) QSqlDatabase::removeDatabase(connectionName); } -void DBHelper::updateImageFilters(qulonglong libraryId, const ComicInfo &comicInfo) +void DBHelper::updateImageFilters(qulonglong libraryId, ComicId comicId, const ComicInfo &comicInfo) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; @@ -1018,7 +1346,7 @@ void DBHelper::updateImageFilters(qulonglong libraryId, const ComicInfo &comicIn QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; - ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); + ComicDB comic = DBHelper::loadComic(comicId.value(), db, found); if (found && comic.info.lastTimeImageFiltersSet.toULongLong() < comicInfo.lastTimeImageFiltersSet.toULongLong()) { QSqlQuery updateComicInfo(db); @@ -1029,11 +1357,11 @@ void DBHelper::updateImageFilters(qulonglong libraryId, const ComicInfo &comicIn updateComicInfo.bindValue(":lastTimeImageFiltersSet", comicInfo.lastTimeImageFiltersSet.toULongLong()); updateComicInfo.bindValue(":imageFiltersJson", comicInfo.imageFiltersJson); - updateComicInfo.bindValue(":id", comic.info.id); + updateComicInfo.bindValue(":id", comic.info.id.value()); auto ret = updateComicInfo.exec(); if (!ret) { - QLOG_ERROR() << "Error updating image filters for comic ID:" << comicInfo.id; + QLOG_ERROR() << "Error updating image filters for comic ID:" << comicId.value(); QLOG_ERROR() << updateComicInfo.lastError().databaseText(); QLOG_ERROR() << updateComicInfo.lastError().text(); } @@ -1045,7 +1373,7 @@ void DBHelper::updateImageFilters(qulonglong libraryId, const ComicInfo &comicIn QSqlDatabase::removeDatabase(connectionName); } -void DBHelper::setComicAsReading(qulonglong libraryId, const ComicInfo &comicInfo) +void DBHelper::setComicAsReading(qulonglong libraryId, ComicId comicId) { QString libraryPath = DBHelper::getLibraries().getPath(libraryId); QString connectionName = ""; @@ -1053,7 +1381,7 @@ void DBHelper::setComicAsReading(qulonglong libraryId, const ComicInfo &comicInf QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); bool found; - ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); + ComicDB comic = DBHelper::loadComic(comicId.value(), db, found); comic.info.hasBeenOpened = true; comic.info.read = comic.info.read || comic.info.currentPage == comic.info.numPages; @@ -1078,48 +1406,14 @@ void DBHelper::updateReadingRemoteProgress(const ComicInfo &comicInfo, QSqlDatab updateComicInfo.bindValue(":currentPage", comicInfo.currentPage); updateComicInfo.bindValue(":hasBeenOpened", comicInfo.hasBeenOpened ? 1 : 0); updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); - updateComicInfo.bindValue(":id", comicInfo.id); + updateComicInfo.bindValue(":id", comicInfo.id.value()); updateComicInfo.bindValue(":rating", comicInfo.rating); updateComicInfo.exec(); updateComicInfo.clear(); } -// server v1 -void DBHelper::updateFromRemoteClient(qulonglong libraryId, const ComicInfo &comicInfo) -{ - QString libraryPath = DBHelper::getLibraries().getPath(libraryId); - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath)); - - bool found; - ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); - - if (comic.info.hash == comicInfo.hash) { - if (comicInfo.currentPage > 0) { - comic.info.currentPage = comicInfo.currentPage; - - if (comic.info.currentPage == comic.info.numPages) - comic.info.read = true; - - comic.info.hasBeenOpened = true; - - if (comic.info.lastTimeOpened.toULongLong() < comicInfo.lastTimeOpened.toULongLong()) - comic.info.lastTimeOpened = comicInfo.lastTimeOpened; - } - - if (comicInfo.rating > 0) - comic.info.rating = comicInfo.rating; - - DBHelper::updateReadingRemoteProgress(comic.info, db); - } - connectionName = db.connectionName(); - } - QSqlDatabase::removeDatabase(connectionName); -} - -QMap> DBHelper::updateFromRemoteClient(const QMap> &comics, bool clientSendsHasBeenOpened, bool clientSendsImageFilters) +QMap> DBHelper::updateFromRemoteClient(const QMap> &comics, bool clientSendsHasBeenOpened, bool clientSendsImageFilters) { QMap> moreRecentComics; @@ -1151,11 +1445,11 @@ QMap> DBHelper::updateFromRemoteClient(const QMap comicInfo.id contains comic id - ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found); + ComicDB comic = DBHelper::loadComic(update.comicId.value(), db, found); if (comic.info.hash == comicInfo.hash) { bool isMoreRecent = false; @@ -1209,7 +1503,7 @@ QMap> DBHelper::updateFromRemoteClient(const QMap DBHelper::updateFromRemoteClientWithHash(const QList &co " WHERE id = :id "); for (const auto &comicInfo : comics) { - ComicInfo info = loadComicInfo(comicInfo.hash, db); + const auto storedInfo = loadComicInfo(comicInfo.hash, db); - if (!info.existOnDb) { + if (!storedInfo) { continue; } + ComicInfo info = *storedInfo; + if (comicInfo.currentPage > 0) { info.currentPage = comicInfo.currentPage; @@ -1283,31 +1579,167 @@ QList DBHelper::updateFromRemoteClientWithHash(const QList &co info.lastTimeOpened = comicInfo.lastTimeOpened; } - if (comicInfo.rating > 0) { - info.rating = comicInfo.rating; - } + if (comicInfo.rating > 0) { + info.rating = comicInfo.rating; + } + + updateComicInfo.bindValue(":read", info.read ? 1 : 0); + updateComicInfo.bindValue(":currentPage", info.currentPage); + updateComicInfo.bindValue(":hasBeenOpened", info.hasBeenOpened ? 1 : 0); + updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); + updateComicInfo.bindValue(":id", info.id.value()); + updateComicInfo.bindValue(":rating", info.rating); + updateComicInfo.exec(); + libraryUpdated = true; + } + + db.commit(); + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + if (libraryUpdated) { + updatedLibraries.append(libraries.getUuid(name)); + } + } + + return updatedLibraries; +} + +std::optional DBHelper::insertListFolder(ListFolderKind kind, const std::optional &parentId, const QString &name, QSqlDatabase &db) +{ + const auto tables = folderTable(kind); + if (parentId && !rowExists(tables.table, *parentId, db)) + return std::nullopt; + + QSqlQuery insert(db); + insert.prepare(QString("INSERT INTO %1 (parent_id, name, ordering) " + "VALUES (:parent_id, :name, (SELECT COALESCE(MAX(ordering) + 1, 0) FROM %1 WHERE parent_id IS :parent_id))") + .arg(tables.table)); + insert.bindValue(":parent_id", parentId ? QVariant(*parentId) : QVariant()); + insert.bindValue(":name", name); + if (!insert.exec()) { + QLOG_ERROR() << "Unable to insert a list folder:" << insert.lastError().text(); + return std::nullopt; + } + return ListFolderId { kind, insert.lastInsertId().toULongLong() }; +} + +bool DBHelper::renameListFolder(const ListFolderId &id, const QString &name, QSqlDatabase &db) +{ + QSqlQuery update(db); + update.prepare(QString("UPDATE %1 SET name = :name WHERE id = :id").arg(folderTable(id.kind).table)); + update.bindValue(":name", name); + update.bindValue(":id", id.id); + if (!update.exec() || update.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to rename a list folder:" << update.lastError().text(); + return false; + } + return true; +} + +bool DBHelper::moveListFolder(const ListFolderId &id, const std::optional &parentId, QSqlDatabase &db) +{ + const auto tables = folderTable(id.kind); + const QVariant parent = parentId ? QVariant(*parentId) : QVariant(); + + db.transaction(); + bool moved = rowExists(tables.table, id.id, db) && (!parentId || rowExists(tables.table, *parentId, db)); + if (moved && parentId) { + // the new parent cannot be the folder or one of the folders below it; the driver binds qulonglong as text, and + // the columns of a CTE have no type to convert it + QSqlQuery below(db); + below.prepare(QString("WITH RECURSIVE subtree(id) AS (SELECT CAST(:id AS INTEGER) UNION ALL SELECT f.id FROM %1 f INNER JOIN subtree s ON f.parent_id = s.id) " + "SELECT 1 FROM subtree WHERE id = CAST(:parent_id AS INTEGER)") + .arg(tables.table)); + below.bindValue(":id", id.id); + below.bindValue(":parent_id", *parentId); + moved = below.exec() && !below.next(); + } + + if (moved) { + QSqlQuery move(db); + move.prepare(QString("UPDATE %1 SET parent_id = :parent_id, " + "ordering = (SELECT COALESCE(MAX(ordering) + 1, 0) FROM %1 WHERE parent_id IS :parent_id AND id <> :id) " + "WHERE id = :id") + .arg(tables.table)); + move.bindValue(":parent_id", parent); + move.bindValue(":id", id.id); + moved = move.exec() && move.numRowsAffected() == 1; + if (!moved) + QLOG_ERROR() << "Unable to move a list folder:" << move.lastError().text(); + } + + if (moved && db.commit()) + return true; + db.rollback(); + return false; +} + +bool DBHelper::moveReadingListToFolder(ReadingListId id, const std::optional &folderId, QSqlDatabase &db) +{ + return moveListToFolder(ListFolderKind::ReadingList, id.value(), folderId, db); +} + +bool DBHelper::moveSmartListToFolder(SmartListId id, const std::optional &folderId, QSqlDatabase &db) +{ + return moveListToFolder(ListFolderKind::SmartList, id.value(), folderId, db); +} + +std::optional DBHelper::removeListFolder(const ListFolderId &id, QSqlDatabase &db) +{ + const auto tables = folderTable(id.kind); - updateComicInfo.bindValue(":read", info.read ? 1 : 0); - updateComicInfo.bindValue(":currentPage", info.currentPage); - updateComicInfo.bindValue(":hasBeenOpened", info.hasBeenOpened ? 1 : 0); - updateComicInfo.bindValue(":lastTimeOpened", QDateTime::currentSecsSinceEpoch()); - updateComicInfo.bindValue(":id", info.id); - updateComicInfo.bindValue(":rating", info.rating); - updateComicInfo.exec(); - libraryUpdated = true; - } + // the placeholders of the entries of every reading list below the folder, child lists included + QSqlQuery placeholderIds(db); + if (id.kind == ListFolderKind::ReadingList) { + placeholderIds.prepare("WITH RECURSIVE folders(id) AS (SELECT CAST(:id AS INTEGER) UNION ALL SELECT f.id FROM reading_list_folder f INNER JOIN folders s ON f.parent_id = s.id), " + "lists(id) AS (SELECT r.id FROM reading_list r WHERE r.folder_id IN (SELECT id FROM folders) " + "UNION ALL SELECT r.id FROM reading_list r INNER JOIN lists l ON r.parentId = l.id) " + "SELECT DISTINCT placeholder_id FROM reading_list_entry WHERE placeholder_id IS NOT NULL AND reading_list_id IN (SELECT id FROM lists)"); + placeholderIds.bindValue(":id", id.id); + } else { + placeholderIds.prepare("SELECT NULL WHERE 0"); + } - db.commit(); - connectionName = db.connectionName(); + // the foreign keys delete the folders, lists, child lists, and entries below it + const auto removal = [&] { + QSqlQuery query(db); + query.prepare(QString("DELETE FROM %1 WHERE id = :id").arg(tables.table)); + query.bindValue(":id", id.id); + if (!query.exec() || query.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to remove a list folder:" << query.lastError().text(); + return false; } - QSqlDatabase::removeDatabase(connectionName); + return true; + }; + return removeWithPlaceholders(placeholderIds, removal, db); +} - if (libraryUpdated) { - updatedLibraries.append(libraries.getUuid(name)); - } +bool DBHelper::updateSmartList(SmartListId id, const QString &name, const QString &query, QSqlDatabase &db) +{ + QSqlQuery update(db); + update.prepare("UPDATE smart_list SET name = :name, query = :query WHERE id = :id"); + update.bindValue(":name", name); + update.bindValue(":query", query); + update.bindValue(":id", id.value()); + if (!update.exec() || update.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to update a smart list:" << update.lastError().text(); + return false; } + return true; +} - return updatedLibraries; +bool DBHelper::removeSmartList(SmartListId id, QSqlDatabase &db) +{ + QSqlQuery remove(db); + remove.prepare("DELETE FROM smart_list WHERE id = :id"); + remove.bindValue(":id", id.value()); + if (!remove.exec() || remove.numRowsAffected() != 1) { + QLOG_ERROR() << "Unable to remove a smart list:" << remove.lastError().text(); + return false; + } + return true; } void DBHelper::renameLabel(qulonglong id, const QString &name, QSqlDatabase &db) @@ -1370,60 +1802,49 @@ void DBHelper::reasignOrderToComicsInFavorites(QList comicIds, QSqlD db.commit(); } -void DBHelper::reasignOrderToComicsInLabel(qulonglong labelId, QList comicIds, QSqlDatabase &db) +bool DBHelper::moveEntriesToComicInfo(ComicInfoId oldComicInfoId, ComicInfoId newComicInfoId, QSqlDatabase &db) { - QSqlQuery updateOrdering(db); - updateOrdering.prepare("UPDATE comic_label SET " - "ordering = :ordering " - "WHERE comic_id = :comic_id AND label_id = :label_id"); - db.transaction(); - int order = 0; - const auto &labelComicIds = comicIds; - for (const auto id : labelComicIds) { - updateOrdering.bindValue(":ordering", order++); - updateOrdering.bindValue(":comic_id", id); - updateOrdering.bindValue(":label_id", labelId); - updateOrdering.exec(); + for (const auto table : { "reading_list_entry", "label_entry" }) { + QSqlQuery move(db); + move.prepare(QString("UPDATE %1 SET comic_info_id = :new_comic_info_id " + "WHERE comic_info_id = :old_comic_info_id " + "AND NOT EXISTS (SELECT 1 FROM comic WHERE comicInfoId = :old_comic_info_id)") + .arg(table)); + move.bindValue(":new_comic_info_id", newComicInfoId.value()); + move.bindValue(":old_comic_info_id", oldComicInfoId.value()); + if (!move.exec()) { + QLOG_ERROR() << "Unable to move the entries of" << table << "to the new comic info:" << move.lastError().text(); + return false; + } } - db.commit(); + return true; } -void DBHelper::reasignOrderToComicsInReadingList(qulonglong readingListId, QList comicIds, QSqlDatabase &db) +QStringList DBHelper::removeUnusedComicInfo(QSqlDatabase &db) { - QSqlQuery updateOrdering(db); - updateOrdering.prepare("UPDATE comic_reading_list SET " - "ordering = :ordering " - "WHERE comic_id = :comic_id AND reading_list_id = :reading_list_id"); - db.transaction(); - int order = 0; - const auto &readingListComicIds = comicIds; - for (const auto id : readingListComicIds) { - updateOrdering.bindValue(":ordering", order++); - updateOrdering.bindValue(":comic_id", id); - updateOrdering.bindValue(":reading_list_id", readingListId); - updateOrdering.exec(); - QLOG_TRACE() << updateOrdering.lastError().databaseText() << "-" << updateOrdering.lastError().driverText(); - } + static const QString unused = "NOT EXISTS (SELECT 1 FROM comic WHERE comicInfoId = comic_info.id) " + "AND NOT EXISTS (SELECT 1 FROM reading_list_entry WHERE comic_info_id = comic_info.id) " + "AND NOT EXISTS (SELECT 1 FROM label_entry WHERE comic_info_id = comic_info.id)"; - db.commit(); -} + QStringList hashes; + QSqlQuery selectUnused(db); + selectUnused.setForwardOnly(true); + if (!selectUnused.exec("SELECT hash FROM comic_info WHERE " + unused)) { + QLOG_ERROR() << "Error getting unused comic info:" << selectUnused.lastError().text(); + return { }; + } + while (selectUnused.next()) { + hashes.append(selectUnused.value(0).toString()); + } -void DBHelper::updateComicsInfo(QList &comics, const QString &databasePath) -{ - QString connectionName = ""; - { - QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - db.open(); - db.transaction(); - const auto &comicsRef = comics; - for (auto comic : comicsRef) { - DBHelper::update(&(comic.info), db); - } - db.commit(); - connectionName = db.connectionName(); + QSqlQuery deleteUnused(db); + if (!deleteUnused.exec("DELETE FROM comic_info WHERE " + unused)) { + QLOG_ERROR() << "Error purging info from comic_info:" << deleteUnused.lastError().text(); + return { }; } - QSqlDatabase::removeDatabase(connectionName); + + return hashes; } bool DBHelper::renameFolder(qulonglong id, const QString &name, const QString &oldPath, const QString &newPath, QSqlDatabase &db, QString *error) @@ -1485,21 +1906,23 @@ qulonglong DBHelper::ensureFolderPath(const QString &relativePath, QSqlDatabase const auto segments = relativePath.split('/', Qt::SkipEmptyParts); qulonglong parentId = 1; - auto inheritedType = DBHelper::loadFolder(parentId, db).type; + auto inheritedType = DBHelper::loadFolder(parentId, db).value_or(Folder()).type; QString currentPath; for (const auto &segment : segments) { currentPath += '/' + segment; const auto existing = DBHelper::loadFolder(segment, parentId, db); - if (existing.knownId) { - parentId = existing.id; - inheritedType = existing.type; + if (existing) { + parentId = existing->id.value(); + inheritedType = existing->type; continue; } - Folder folder(segment, currentPath); - folder.setFather(parentId); + Folder folder; + folder.name = segment; + folder.path = currentPath; + folder.parentId = FolderId(parentId); folder.type = inheritedType; parentId = DBHelper::insert(&folder, db); @@ -1625,7 +2048,7 @@ qulonglong DBHelper::insert(Folder *folder, QSqlDatabase &db) QSqlQuery query(db); query.prepare("INSERT INTO folder (parentId, name, path, added, type) " "VALUES (:parentId, :name, :path, :added, :type)"); - query.bindValue(":parentId", folder->parentId); + query.bindValue(":parentId", folder->parentId.value()); query.bindValue(":name", folder->name); query.bindValue(":path", folder->path); query.bindValue(":added", added); @@ -1640,7 +2063,7 @@ qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo { auto added = QDateTime::currentSecsSinceEpoch(); - if (!comic->info.existOnDb) { + if (comic->info.id == ComicInfoId()) { QSqlQuery comicInfoInsert(db); comicInfoInsert.prepare("INSERT INTO comic_info (hash,numPages,coverSizeRatio,originalCoverSize,added,type) " @@ -1653,23 +2076,21 @@ qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo auto intType = static_cast(comic->info.type.value()); comicInfoInsert.bindValue(":type", intType); comicInfoInsert.exec(); - comic->info.id = comicInfoInsert.lastInsertId().toULongLong(); + comic->info.id = ComicInfoId(comicInfoInsert.lastInsertId().toULongLong()); comic->info.added = added; - comic->_hasCover = false; if (insertAllInfo) { DBHelper::update(&(comic->info), db); // TODO use insert to insert all the info values, the common binding need to be extracted and shared between update and insert } - } else - comic->_hasCover = true; + } QSqlQuery query(db); query.prepare("INSERT INTO comic (parentId, comicInfoId, fileName, path) " "VALUES (:parentId,:comicInfoId,:name, :path)"); - query.bindValue(":parentId", comic->parentId); - query.bindValue(":comicInfoId", comic->info.id); - query.bindValue(":name", comic->name); - query.bindValue(":path", comic->path); + query.bindValue(":parentId", comic->file.folderId.value()); + query.bindValue(":comicInfoId", comic->info.id.value()); + query.bindValue(":name", comic->file.fileName); + query.bindValue(":path", comic->file.path); query.exec(); // loop through parents and update their updated field @@ -1678,14 +2099,14 @@ qulonglong DBHelper::insert(ComicDB *comic, QSqlDatabase &db, bool insertAllInfo updateFolder.prepare("UPDATE folder SET " "updated = :updated " "WHERE id = :id "); - auto currentParentId = comic->parentId; + auto currentParentId = comic->file.folderId.value(); while (currentParentId != 1 && currentParentId != 0) { updateFolder.bindValue(":updated", added); updateFolder.bindValue(":id", currentParentId); updateFolder.exec(); - auto f = loadFolder(currentParentId, db); - currentParentId = f.parentId; + const auto f = loadFolder(currentParentId, db); + currentParentId = f ? f->parentId.value() : 0; } //---- @@ -1704,6 +2125,18 @@ qulonglong DBHelper::insertLabel(const QString &name, YACReader::LabelColors col return query.lastInsertId().toULongLong(); } +std::optional DBHelper::insertPlaceholder(const Placeholder &placeholder, QSqlDatabase &db) +{ + QSqlQuery query(db); + query.prepare("INSERT INTO placeholder (" + TableProjection::insertColumns(Placeholder::columns()) + ") VALUES (" + TableProjection::insertValues(Placeholder::columns()) + ")"); + placeholder.bindTo(query); + if (!query.exec()) { + QLOG_ERROR() << "Unable to insert a placeholder:" << query.lastError().text(); + return std::nullopt; + } + return PlaceholderId(query.lastInsertId().toULongLong()); +} + qulonglong DBHelper::insertReadingList(const QString &name, QSqlDatabase &db) { QSqlQuery query(db); @@ -1714,6 +2147,20 @@ qulonglong DBHelper::insertReadingList(const QString &name, QSqlDatabase &db) return query.lastInsertId().toULongLong(); } +std::optional DBHelper::insertSmartList(const QString &name, const QString &query, QSqlDatabase &db) +{ + QSqlQuery insert(db); + insert.prepare("INSERT INTO smart_list (name, query, ordering) " + "VALUES (:name, :query, (SELECT COALESCE(MAX(ordering) + 1, 0) FROM smart_list))"); + insert.bindValue(":name", name); + insert.bindValue(":query", query); + if (!insert.exec()) { + QLOG_ERROR() << "Unable to insert a smart list:" << insert.lastError().text(); + return std::nullopt; + } + return SmartListId(insert.lastInsertId().toULongLong()); +} + qulonglong DBHelper::insertReadingSubList(const QString &name, qulonglong parentId, int ordering, QSqlDatabase &db) { QSqlQuery query(db); @@ -1740,32 +2187,7 @@ void DBHelper::insertComicsInFavorites(const QList &comicsList, QSqlDat "VALUES (1, :comic_id, :ordering)"); for (const auto &comic : comicsList) { - query.bindValue(":comic_id", comic.id); - query.bindValue(":ordering", numComics++); - query.exec(); - } - - QLOG_TRACE() << query.lastError(); - - db.commit(); -} - -void DBHelper::insertComicsInLabel(const QList &comicsList, qulonglong labelId, QSqlDatabase &db) -{ - QSqlQuery getNumComics(QString("SELECT count(*) FROM comic_label WHERE label_id = %1;").arg(labelId), db); - getNumComics.next(); - - int numComics = getNumComics.value(0).toInt(); - - db.transaction(); - - QSqlQuery query(db); - query.prepare("INSERT INTO comic_label (label_id, comic_id, ordering) " - "VALUES (:label_id, :comic_id, :ordering)"); - - for (const auto &comic : comicsList) { - query.bindValue(":label_id", labelId); - query.bindValue(":comic_id", comic.id); + query.bindValue(":comic_id", comic.file.id.value()); query.bindValue(":ordering", numComics++); query.exec(); } @@ -1775,83 +2197,34 @@ void DBHelper::insertComicsInLabel(const QList &comicsList, qulonglong db.commit(); } -void DBHelper::insertComicsInReadingList(const QList &comicsList, qulonglong readingListId, QSqlDatabase &db) -{ - QSqlQuery getNumComics("SELECT count(*) FROM comic_reading_list;", db); - getNumComics.next(); - - int numComics = getNumComics.value(0).toInt(); - - db.transaction(); - - QSqlQuery query(db); - query.prepare("INSERT INTO comic_reading_list (reading_list_id, comic_id, ordering) " - "VALUES (:reading_list_id, :comic_id, :ordering)"); - - for (const auto &comic : comicsList) { - query.bindValue(":reading_list_id", readingListId); - query.bindValue(":comic_id", comic.id); - query.bindValue(":ordering", numComics++); - query.exec(); - } - - db.commit(); -} - // queries -QList DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) +QList DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) { - QList list; + QList list; QSqlQuery selectQuery(db); // TODO check - selectQuery.prepare("SELECT * FROM folder WHERE parentId = :parentId and id <> 1"); + selectQuery.prepare("SELECT " + TableProjection::selectList("f", Folder::columns()) + " FROM folder f WHERE f.parentId = :parentId and f.id <> 1"); selectQuery.bindValue(":parentId", parentId); selectQuery.exec(); - QSqlRecord record = selectQuery.record(); - - int name = record.indexOf("name"); - int path = record.indexOf("path"); - int finished = record.indexOf("finished"); - int completed = record.indexOf("completed"); - int id = record.indexOf("id"); - int numChildren = record.indexOf("numChildren"); - int firstChildHash = record.indexOf("firstChildHash"); - int customImage = record.indexOf("customImage"); - int type = record.indexOf("type"); - int added = record.indexOf("added"); - int updated = record.indexOf("updated"); - - Folder *currentItem; + const auto indexes = Folder::columnIndexes(selectQuery.record(), "f"); while (selectQuery.next()) { // TODO sort by sort indicator and name - currentItem = new Folder(selectQuery.value(id).toULongLong(), parentId, selectQuery.value(name).toString(), selectQuery.value(path).toString()); - - currentItem->finished = selectQuery.value(finished).toBool(); - currentItem->completed = selectQuery.value(completed).toBool(); - if (!selectQuery.value(numChildren).isNull() && selectQuery.value(numChildren).isValid()) { - currentItem->numChildren = selectQuery.value(numChildren).toInt(); - } - currentItem->firstChildHash = selectQuery.value(firstChildHash).toString(); - currentItem->customImage = selectQuery.value(customImage).toString(); - currentItem->type = selectQuery.value(type).value(); - currentItem->added = selectQuery.value(added).toLongLong(); - currentItem->updated = selectQuery.value(updated).toLongLong(); + const auto currentItem = Folder::fromQuery(selectQuery, indexes); int lessThan = 0; if (list.isEmpty() || !sort) list.append(currentItem); else { - auto last = static_cast(list.back()); - QString nameLast = last->name; - QString nameCurrent = currentItem->name; - QList::iterator i; + QString nameLast = list.back().name; + QString nameCurrent = currentItem.name; + QList::iterator i; i = list.end(); i--; while ((0 > (lessThan = naturalCompare(nameCurrent, nameLast, Qt::CaseInsensitive))) && i != list.begin()) { i--; - nameLast = (*i)->name; + nameLast = i->name; } if (lessThan >= 0) // si se ha encontrado un elemento menor que current, se inserta justo después list.insert(++i, currentItem); @@ -1863,6 +2236,39 @@ QList DBHelper::getFoldersFromParent(qulonglong parentId, QSqlDat return list; } +std::optional> DBHelper::loadSubfolders(FolderId parentId, QSqlDatabase &db) +{ + QSqlQuery selectQuery(db); + selectQuery.setForwardOnly(true); + selectQuery.prepare("SELECT " + TableProjection::selectList("f", Folder::columns()) + " FROM folder f WHERE f.parentId = :parentId AND f.id <> 1 ORDER BY f.name"); + selectQuery.bindValue(":parentId", parentId.value()); + if (!selectQuery.exec()) + return std::nullopt; + + QList folders; + const auto indexes = Folder::columnIndexes(selectQuery.record(), "f"); + while (selectQuery.next()) + folders.append(Folder::fromQuery(selectQuery, indexes)); + // FolderItem::appendChild inserts each child by name in the same way + std::stable_sort(folders.begin(), folders.end(), [](const Folder &a, const Folder &b) { return naturalSortLessThanCI(a.name, b.name); }); + return folders; +} + +bool DBHelper::readingListExists(ReadingListId id, QSqlDatabase &db) +{ + return rowExists("reading_list", id.value(), db); +} + +bool DBHelper::listFolderExists(const ListFolderId &id, QSqlDatabase &db) +{ + return rowExists(folderTable(id.kind).table, id.id, db); +} + +bool DBHelper::labelExists(LabelId id, QSqlDatabase &db) +{ + return rowExists("label", id.value(), db); +} + QList DBHelper::getSortedComicsFromParent(qulonglong parentId, QSqlDatabase &db) { QList list; @@ -1870,64 +2276,33 @@ QList DBHelper::getSortedComicsFromParent(qulonglong parentId, QSqlData QSqlQuery selectQuery(db); selectQuery.setForwardOnly(true); - selectQuery.prepare("select * from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.parentId = :parentId"); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE c.parentId = :parentId"); selectQuery.bindValue(":parentId", parentId); selectQuery.exec(); - QSqlRecord record = selectQuery.record(); - - int id = record.indexOf("id"); - // int parentIdIndex = record.indexOf("parentId"); - int fileName = record.indexOf("fileName"); - int path = record.indexOf("path"); - - ComicDB currentItem; - while (selectQuery.next()) { - currentItem.id = selectQuery.value(id).toULongLong(); - currentItem.parentId = parentId; // selectQuery.value(parentId).toULongLong(); - currentItem.name = selectQuery.value(fileName).toString(); - currentItem.path = selectQuery.value(path).toString(); - - currentItem.info = getComicInfoFromQuery(selectQuery, "comicInfoId"); - - list.append(currentItem); - } + list = comicsFromQuery(selectQuery); std::sort(list.begin(), list.end(), [](const ComicDB &c1, const ComicDB &c2) { - return comicNumberLessThan(c1.info.number, c1.name, c2.info.number, c2.name); + return comicNumberLessThan(c1.info.number, c1.file.fileName, c2.info.number, c2.file.fileName); }); // selectQuery.finish(); return list; } -QList DBHelper::getComicsFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) +QList DBHelper::getComicsFromParent(qulonglong parentId, QSqlDatabase &db, bool sort) { - QList list; + QList list; QSqlQuery selectQuery(db); - selectQuery.prepare("select c.id,c.parentId,c.fileName,c.path,ci.hash from comic c inner join comic_info ci on (c.comicInfoId = ci.id) where c.parentId = :parentId"); + selectQuery.prepare("SELECT " + ComicDB::selectList("c", "ci") + " FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE c.parentId = :parentId"); selectQuery.bindValue(":parentId", parentId); selectQuery.exec(); - QSqlRecord record = selectQuery.record(); - - int id = record.indexOf("id"); - - ComicDB *currentItem; - while (selectQuery.next()) { - currentItem = new ComicDB(); - currentItem->id = selectQuery.value(id).toULongLong(); - currentItem->parentId = selectQuery.value(1).toULongLong(); - currentItem->name = selectQuery.value(2).toString(); - currentItem->path = selectQuery.value(3).toString(); - currentItem->info = DBHelper::loadComicInfo(selectQuery.value(4).toString(), db); - - list.append(currentItem); - } + list = comicsFromQuery(selectQuery); if (sort) { - std::sort(list.begin(), list.end(), [](const LibraryItem *c1, const LibraryItem *c2) { - return c1->name.localeAwareCompare(c2->name) < 0; + std::sort(list.begin(), list.end(), [](const ComicDB &c1, const ComicDB &c2) { + return c1.file.fileName.localeAwareCompare(c2.file.fileName) < 0; }); } @@ -1942,30 +2317,26 @@ QList ApiKeyDialog - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Bevor du dich mit Comic Vine verbindest, brauchst du deinen eigenen API-Schlüssel. Du kannst <a href="http://www.comicvine.com/api/">hier</a> einen kostenlosen bekommen. + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Bevor Sie sich mit Comic Vine verbinden können, benötigen Sie einen eigenen API-Schlüssel. Einen kostenlosen erhalten Sie <a href="http://www.comicvine.com/api/">hier</a> Paste here your Comic Vine API key - Füge hier deinen Comic Vine API-Schlüssel ein. + Fügen Sie hier Ihren Comic-Vine-API-Schlüssel ein @@ -93,12 +153,12 @@ System - Systemumgebung + System Light - Helligkeit + Hell @@ -108,7 +168,7 @@ Custom - Brauch + Benutzerdefiniert @@ -118,12 +178,12 @@ Remove this user-imported theme - Entfernen Sie dieses vom Benutzer importierte Design + Dieses vom Benutzer importierte Design entfernen Light: - Licht: + Hell: @@ -138,37 +198,37 @@ Import theme... - Theme importieren... + Design importieren... Theme - Thema + Design Theme editor - Theme-Editor + Design-Editor Open Theme Editor... - Theme-Editor öffnen... + Design-Editor öffnen... Theme editor error - Fehler im Theme-Editor + Fehler im Design-Editor The current theme JSON could not be loaded. - Der aktuelle Theme-JSON konnte nicht geladen werden. + Das JSON des aktuellen Designs konnte nicht geladen werden. Import theme - Thema importieren + Design importieren @@ -179,7 +239,7 @@ Could not import theme from: %1 - Theme konnte nicht importiert werden von: + Das Design konnte nicht aus folgender Datei importiert werden: %1 @@ -188,7 +248,7 @@ %1 %2 - Theme konnte nicht importiert werden von: + Das Design konnte nicht aus folgender Datei importiert werden: %1 %2 @@ -196,136 +256,658 @@ Import failed - Der Import ist fehlgeschlagen + Import fehlgeschlagen - ClassicComicsView + CblEntriesView - - Hide comic flow - Comic Flow ausblenden + + Find comic › + Comic suchen › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Kopieren von Comics... + + Series + Serie - Moving comics... - Verschieben von Comics... + + Number + Nummer + + + + Volume + Band + + + + Year + Jahr + + + + Format + Format + + + + + ComicVine ID + Comic Vine-ID + + + + series and number + Serie und Nummer + + + + series without the volume, and number + Serie ohne Band und Nummer + + + + letters and digits of the series, and number + Buchstaben und Ziffern der Serie und Nummer + + + + series with its year, and number + Serie mit Jahr und Nummer + + + + volume %1 + Band %1 + + + + Comic in your library + Comic in Ihrer Bibliothek + + + + Missing in your library + Fehlt in Ihrer Bibliothek + + + + Changed by you. + Von Ihnen geändert. + + + + The entry has this comic. + Dem Eintrag ist dieser Comic zugeordnet. + + + + Matched by %1. + Zugeordnet anhand von %1. + + + + Your library already has this comic as missing. Matched by %1. + Dieser Comic ist in Ihrer Bibliothek bereits als fehlend erfasst. Zugeordnet anhand von %1. + + + + Possible comic %1 + Möglicher Comic %1 + + + + + Changed by you: it is imported as missing. + Von Ihnen geändert: wird als fehlend importiert. + + + + The comics have the same ComicVine ID. + Die Comics haben dieselbe Comic Vine-ID. + + + + The comic has the same series and number, but its volume or year is different. + Der Comic hat dieselbe Serie und Nummer, aber einen anderen Band oder ein anderes Jahr. + + + + The comics have the same series and number. + Die Comics haben dieselbe Serie und Nummer. + + + + Values that are different from the file are in amber. + Vom Dateiinhalt abweichende Werte sind bernsteinfarben. + + + + Closest series in your library + Ähnlichste Serien in Ihrer Bibliothek + + + + Library + Bibliothek + + + + Has %1 + Enthält %1 + + + + No comic found. Tried: %1. + Kein Comic gefunden. Versucht: %1. + + + + In the file + In der Datei + + + + Entry %1 of %2 + Eintrag %1 von %2 + + + + Use this comic + Diesen Comic verwenden + + + + Use this missing comic + Diesen fehlenden Comic verwenden + + + + CblFindComicPage + + + ‹ All entries + ‹ Alle Einträge + + + + Previous problem + Vorheriges Problem + + + + Next problem + Nächstes Problem + + + + Folders + Ordner + + + + Search the whole library + Gesamte Bibliothek durchsuchen + + + + File + Datei + + + + Series + Serie + + + + No. + Nr. + + + + Volume + Band + + + + Year + Jahr + + + + Go to the next problem after I choose + Nach meiner Auswahl zum nächsten Problem wechseln + + + + Use this comic + Diesen Comic verwenden + + + + Cancel + Abbrechen + + + + Search in this folder + In diesem Ordner suchen + + + + Entry %1 of %2 + Eintrag %1 von %2 + + + + Missing: no comic found. Browse or search the library. + Fehlt: kein Comic gefunden. Durchsuchen Sie die Bibliothek. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Mehrdeutig: %n möglicher Comic. Durchsuchen Sie die Bibliothek. + Mehrdeutig: %n mögliche Comics. Durchsuchen Sie die Bibliothek. + + + + + Browse or search the library to choose another comic. + Durchsuchen Sie die Bibliothek, um einen anderen Comic auszuwählen. + + + + Problem %1 of %2 + Problem %1 von %2 + + + + %n problem(s) + + %n Problem + %n Probleme + + + + + + + Library + Bibliothek + + + + Search results + Suchergebnisse + + + + CblImportDialog + + + Match to library comics — %1 + Bibliothekscomics zuordnen — %1 + + + + Apply + Anwenden + + + + Close + Schließen + + + + Skip + Überspringen + + + + Import + Importieren + + + + &Name: + &Name: + + + + A reading list with this name already exists. Import creates another one. + Eine Leseliste mit diesem Namen ist bereits vorhanden. Beim Import wird eine weitere erstellt. + + + + Filter entries + Einträge filtern + + + + Matching the books of %1 with your library... + Bücher aus %1 werden Ihrer Bibliothek zugeordnet... + + + + Matching the entries of %1 with your library... + Einträge aus %1 werden Ihrer Bibliothek zugeordnet... + + + + + Match to library comics + Bibliothekscomics zuordnen + + + + This also gives comics to entries in %n other list(s): %1 + + Dadurch werden auch Einträgen in %n weiterer Liste Comics zugeordnet: %1 + Dadurch werden auch Einträgen in %n weiteren Listen Comics zugeordnet: %1 + + + + + The changes could not be applied. Nothing was changed. + Die Änderungen konnten nicht angewendet werden. Es wurde nichts geändert. + + + + The file cannot be opened. + Die Datei kann nicht geöffnet werden. + + + + The file is damaged (line %1, column %2). + Die Datei ist beschädigt (Zeile %1, Spalte %2). + + + + The file is not a reading list. + Die Datei ist keine Leseliste. + + + + The library cannot be read. + Die Bibliothek kann nicht gelesen werden. + + + + Import reading lists + Leselisten importieren + + + + The reading list could not be imported. Nothing was written. + Die Leseliste konnte nicht importiert werden. Es wurde nichts geschrieben. + + + + Import reading lists — %1 (%2 of %3) + Leselisten importieren — %1 (%2 von %3) + + + + Import reading lists — %1 + Leselisten importieren — %1 + + + + All (%1) + Alle (%1) + + + + Matched (%1) + Zugeordnet (%1) + + + + Ambiguous (%1) + Mehrdeutig (%1) + + + + Missing (%1) + Fehlend (%1) + + + + Changed by you (%1) + Von Ihnen geändert (%1) + + + + %1 of %n have a comic + + %1 von %n hat einen Comic + %1 von %n haben einen Comic + + + + + Find comic... + Comic suchen... + + + + Use the original match + Ursprüngliche Zuordnung verwenden + + + + Make missing + Als fehlend markieren + + + + Import as missing + Als fehlend importieren + + + + CblImportModel + + + Matched + Zugeordnet + + + + Ambiguous + Mehrdeutig + + + + Missing + Fehlend + + + + Changed + Geändert + + + + # + # + + + + In the file + In der Datei + + + + Comic in your library + Comic in Ihrer Bibliothek + + + + Status + Status + + + + Already missing in your library + In Ihrer Bibliothek bereits als fehlend erfasst + + + + %n possible comic(s) + + %n möglicher Comic + %n mögliche Comics + + + + + Not in library + Nicht in der Bibliothek + + + + CblMatchComicDialog + + + Match to library comic + Bibliothekscomic zuordnen + + + + Loading the library... + Bibliothek wird geladen... + + + + The library cannot be read. + Die Bibliothek kann nicht gelesen werden. + + + + ClassicComicsView + + + Hide comic flow + Comic Flow ausblenden ComicInfoView - + Main character or team Hauptfigur oder Team - + Teams - Mannschaften + Teams - + Locations - Standorte + Schauplätze - + Authors Autoren - + writer - Schriftsteller + Autor - + penciller - Künstler (Bleistift) + Zeichner - + inker - Künstler (Tinte) + Tuscher - + colorist - Künstler (Farbe) + Kolorist - + letterer - Künstler (Lettering) + Letterer - + cover artist - Künstler (Cover) + Coverzeichner - + editor - Redakteur + Lektor - + imprint Imprint - + Publisher - Herausgeber + Verlag - + color Farbe - + b/w Schwarzweiß - + Characters - Charaktere + Figuren ComicManagementCoordinator - - + + YACReader not found YACReader nicht gefunden - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader nicht gefunden. YACReader muss im gleichen Ordner installiert sein wie YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader nicht gefunden. Eventuell besteht ein Problem mit Ihrer YACReader-Installation. - + Error Fehler - + Error opening comic with third party reader. Beim Öffnen des Comics mit dem Drittanbieter-Reader ist ein Fehler aufgetreten. @@ -333,67 +915,67 @@ ComicModel - + no Nein - + yes Ja - + Read - Lesen + Gelesen - + Series Serie - + Volume - Volumen + Band - + Story Arc Handlungsbogen - + Size Größe - + Pages Seiten - + Title Titel - + Current Page Aktuelle Seite - + File Name Dateiname - + Publication Date Veröffentlichungsdatum - + Rating Bewertung @@ -401,68 +983,112 @@ ComicVineDialog - + back zurück - + next nächste - + skip überspringen - + close schließen - - - Retrieving tags for : %1 - Herunterladen von Tags für : %1 + + + Retrieving tags for: %1 + Herunterladen von Tags für: %1 + + + + Creating missing comic %1 of %2 + Fehlender Comic %1 von %2 wird erstellt + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine hat für %n Comic nicht geantwortet. + Comic Vine hat für %n Comics nicht geantwortet. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n fehlender Comic hat nur die Grunddaten des Bandes erhalten. + %n fehlende Comics haben nur die Grunddaten des Bandes erhalten. + + + + + %n missing comic(s) have no cover. + + %n fehlender Comic hat kein Cover. + %n fehlende Comics haben kein Cover. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine erlaubt etwa 200 Anfragen pro Stunde. Versuchen Sie es für die nicht aktualisierten Comics später erneut. + + + + Try again later for the comics that were not updated. + Versuchen Sie es für die nicht aktualisierten Comics später erneut. + + + + Comic Vine + Comic Vine - + Looking for comic... Suche nach Comic... - + search - suche + suchen - - - + + + Looking for volume... - Suche nach Band.... + Suche nach Band... - - + + comic %1 of %2 - %3 Comic %1 von %2 - %3 - + %1 comics selected - %1 Comic ausgewählt + %1 Comics ausgewählt - - Error connecting to ComicVine - Fehler bei Verbindung zu ComicVine + + Error connecting to Comic Vine + Fehler bei Verbindung zu Comic Vine - + Retrieving volume info... - Herunterladen von Info zu Ausgabe... + Bandinformationen werden abgerufen... @@ -478,7 +1104,7 @@ Create new library - Erstelle eine neue Bibliothek + Neue Bibliothek erstellen @@ -492,23 +1118,23 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. Es kann einige Minuten dauern, eine neue Bibliothek zu erstellen. Sie können den Prozess abbrechen und die Bibliothek später aktualisieren, um den Vorgang abzuschließen. The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - Der gewählte Pfad existiert nicht oder ist kein gültiger Pfad. Stellen Sie sicher, dass Sie Schreibzugriff zu dem Ordner haben + Der gewählte Pfad existiert nicht oder ist kein gültiger Pfad. Stellen Sie sicher, dass Sie Schreibzugriff auf den Ordner haben - Comics folder : - Comics-Ordner : + Comics folder: + Comics-Ordner: - Library Name : - Bibliothek-Name : + Library Name: + Bibliotheksname: @@ -519,11 +1145,19 @@ DBHelper - + The folder entry could not be found in the library database. Der Ordnereintrag wurde in der Datenbank der Bibliothek nicht gefunden. + + DelayedProgressDialog + + + Cancel + Abbrechen + + EditShortcutsDialog @@ -533,8 +1167,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - Um eine Tastenkombination zu ändern, doppelklicken Sie auf die Tastenkombination und geben Sie die neuen Tasten ein. + To change a shortcut, double-click the key combination and type the new keys. + Um ein Kürzel zu ändern, doppelklicken Sie auf die Tastenkombination und geben Sie die neuen Tasten ein. @@ -544,11 +1178,11 @@ Shortcut in use - Verwendete Kürzel + Kürzel wird verwendet - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function Das Kürzel "%1" ist bereits für eine andere Funktion in Verwendung @@ -576,9 +1210,17 @@ EmptyLabelWidget - - This label doesn't contain comics yet - Dieses Label enthält noch keine Comics + + This label doesn't contain comics yet + Dieses Label enthält noch keine Comics + + + + EmptyListFolderWidget + + + This folder is empty + Dieser Ordner ist leer @@ -611,13 +1253,13 @@ ExportComicsInfoDialog - Output file : - Zieldatei : + Output file: + Zieldatei: Destination database name - Ziel-Datenbank Name + Name der Zieldatenbank @@ -632,12 +1274,12 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Der gewählte Pfad existiert nicht oder ist kein gültiger Pfad. Stellen Sie sicher, dass Sie Schreibzugriff zu dem Ordner haben + Der gewählte Pfad für die Zieldatei existiert nicht oder ist kein gültiger Pfad. Stellen Sie sicher, dass Sie Schreibzugriff auf den Ordner haben Export comics info - Comicinfo exportieren + Comic-Infos exportieren @@ -660,12 +1302,12 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Der gewählte Pfad existiert nicht oder ist kein gültiger Pfad. Stellen Sie sicher, dass Sie Schreibzugriff zu dem Ordner haben + Der gewählte Pfad für die Zieldatei existiert nicht oder ist kein gültiger Pfad. Stellen Sie sicher, dass Sie Schreibzugriff auf den Ordner haben - Output folder : - Ziel-Ordner : + Output folder: + Zielordner: @@ -675,43 +1317,36 @@ Create covers package - Erzeuge Titelbild-Paket + Cover-Paket erstellen Destination directory - Ziel-Verzeichnis + Zielordner FileComic - - + + Format not supported Format nicht unterstützt - + 7z not found 7z nicht gefunden - + Unknown error opening the file Unbekannter Fehler beim Öffnen der Datei - + CRC error on page (%1): some of the pages will not be displayed correctly - CRC Fehler auf Seite (%1): einige Seiten werden nicht korrekt dargestellt - - - - FolderContentView - - Continue Reading... - Weiterlesen... + CRC-Fehler auf Seite (%1): Einige Seiten werden nicht korrekt dargestellt @@ -739,7 +1374,7 @@ Read - Lesen + Gelesen @@ -759,7 +1394,7 @@ In progress - In Bearbeitung + Begonnen @@ -782,59 +1417,43 @@ Folder name: - Ordnername + Ordnername: GridComicsView - + Show info Info anzeigen - Library - Bibliothek - - - Folder - Ordner - - - Favorites - Favoriten - - - Recently added - Kürzlich hinzugefügt - - - + Manga Manga - + Western manga - Westlicher Manga + Western-Manga - + Web comic Webcomic - + Yonkoma Yonkoma - + Comic Comic - + Unknown Unbekannt @@ -876,26 +1495,26 @@ - Info database location : - Info-Datenbank Speicherort : + Info database location: + Info-Datenbank-Speicherort: Import comics info - Importiere Comic-Info + Comic-Infos importieren Comics info file (*.ydb) - Comics-Info-Datei (*.ydb) + Comic-Info-Datei (*.ydb) ImportLibraryDialog - Destination folder : - Zielordner : + Destination folder: + Zielordner: @@ -909,18 +1528,18 @@ - Compresed library covers (*.clc) - Komprimierte Bibliothek Cover (*.clc) + Compressed library covers (*.clc) + Komprimierte Bibliothekscover (*.clc) - Package location : - Paket Ort : + Package location: + Paketspeicherort: - Library Name : - Bibliothek-Name : + Library Name: + Bibliotheksname: @@ -942,8 +1561,8 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderLibrary erstellt nun eine neue Bibliothek. </p><p>Es kann einige Minuten dauern, eine neue Bibliothek zu erstellen. Sie können den Prozess abbrechen und die Bibliothek später aktualisieren, um den Vorgang abzuschließen.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary erstellt nun eine neue Bibliothek.</p><p>Es kann einige Minuten dauern, eine neue Bibliothek zu erstellen. Sie können den Prozess abbrechen und die Bibliothek später aktualisieren, um den Vorgang abzuschließen.</p> @@ -957,8 +1576,8 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>Die aktuelle Bibliothek wird gerade aktualisiert. Für eine schnellere Aktualisierung, aktualisieren Sie die Bibliothek bitte regelmäßig.</p><p>Sie können den Prozess abbrechen und mit der Aktualisierung später fortfahren.<p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>Die aktuelle Bibliothek wird gerade aktualisiert. Für schnellere Aktualisierungen aktualisieren Sie Ihre Bibliotheken bitte regelmäßig.</p><p>Sie können den Vorgang abbrechen und die Aktualisierung dieser Bibliothek später fortsetzen.</p> @@ -968,7 +1587,7 @@ <p>The current library is being upgraded, please wait.</p> - Die aktuelle Bibliothek wird gerade upgegradet, bitte warten. + <p>Für die aktuelle Bibliothek wird gerade ein Upgrade durchgeführt, bitte warten.</p> @@ -977,7 +1596,7 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> <p>Die aktuelle Bibliothek wird nach alten XML-Metadateninformationen durchsucht.</p><p>Dies ist nur einmal erforderlich und nur, wenn die Bibliothek mit YACReaderLibrary 9.8.2 oder früher erstellt wurde.</p> @@ -986,6 +1605,36 @@ <p>Die aktuelle Bibliothek wird auf fehlende Cover und unvollständige Comic-Informationen überprüft.</p><p>Dies kann mehrere Minuten dauern. Sie können den Vorgang stoppen und später erneut ausführen.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Der Bibliotheksdatenordner konnte nicht erstellt werden: %1. Prüfen Sie den Ordnerzugriff und den freien Speicherplatz. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Der Bibliotheksdatenordner fehlt: %1. Stellen Sie ihn wieder her oder erstellen Sie die Bibliothek vor der Aktualisierung neu. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Die Bibliotheksdatenbank fehlt: %1. Stellen Sie eine Sicherung wieder her oder erstellen Sie die Bibliothek vor der Aktualisierung neu. + + + + + Unable to start a library database transaction: %1 + Eine Bibliotheksdatenbanktransaktion konnte nicht gestartet werden: %1 + + + + + Unable to commit the library database: %1 + Die Bibliotheksdatenbanktransaktion konnte nicht bestätigt werden: %1 + + LibraryInfoView @@ -1009,160 +1658,244 @@ Anzahl der gelesenen Comics + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Diese Bibliothek ist belegt. Warten Sie, bis die Wartung abgeschlossen ist. Falls sie festzuhängen scheint, schließen Sie die unter Details angezeigte Anwendung und versuchen Sie es erneut. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Eine frühere Wartung hat diese Bibliothek möglicherweise nicht freigegeben. Vergewissern Sie sich vor der Wiederherstellung des Zugriffs, dass kein Vorgang von YACReaderLibrary oder YACReaderLibraryServer darauf zugreift, auch nicht auf einem anderen Computer. + + + + Retry + Erneut versuchen + + + + Recover and retry + Zugriff wiederherstellen und erneut versuchen + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Der Besitzer der Bibliothekssperre hat sich geändert. Wiederholen Sie den Vorgang und prüfen Sie den neuen Besitzer, bevor Sie den Zugriff wiederherstellen. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Die Wartungssperre konnte nicht entfernt werden: %1. Vergewissern Sie sich, dass die Datei nicht schreibgeschützt ist und Sie in den Bibliotheksdatenordner schreiben können. Versuchen Sie es dann erneut. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Ein Ordner belegt den Pfad der Wartungssperrdatei: %1. Benennen Sie diesen Ordner um oder verschieben Sie ihn, bevor Sie es erneut versuchen. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Die Bibliothekssperrdatei konnte nicht erstellt werden: %1. Prüfen Sie die Schreibrechte für den Bibliotheksdatenordner. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Der Bibliotheksdatenordner fehlt: %1. Stellen Sie den Ordner wieder her oder erstellen Sie die Bibliothek vor der Aktualisierung neu. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + Die Bibliothek hat eine Wartungssperre, deren Besitzer nicht überprüft werden konnte (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Ein anderer Wartungsvorgang verwendet diese Bibliothek (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Die Bibliothekssperrdatei konnte nicht erstellt werden: %1. Prüfen Sie den freien Speicherplatz und den Zugriff auf den Bibliotheksdatenordner. + + LibraryManagementCoordinator - + Error opening the library Fehler beim Öffnen der Bibliothek - + Error creating the library Fehler beim Erstellen der Bibliothek - + Error updating the library - Fehler beim Updaten der Bibliothek + Fehler beim Aktualisieren der Bibliothek + + + + + XML information rescan + XML-Informationen erneut einlesen + + + + Restore recovery + Wiederherstellung fortsetzen + + + + Library upgrade + Bibliothek aktualisieren + + + + Library maintenance + Bibliothekswartung + + + + Recover library access + Bibliothekszugriff wiederherstellen LibraryWindow - + The selected folder doesn't contain any library. Der ausgewählte Ordner enthält keine Bibliothek. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - Diese Bibliothek wurde mit einer älteren Version von YACReader erzeugt. Sie muss geupdated werden. Jetzt updaten? - - - Remove and delete metadata - Entferne und lösche Metadaten + Diese Bibliothek wurde mit einer älteren Version von YACReaderLibrary erstellt. Sie muss aktualisiert werden. Jetzt aktualisieren? - + Old library Alte Bibliothek - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - Die Bibliothek wurde mit einer neueren Version von YACReader erstellt. Die neue Version jetzt herunterladen? + Diese Bibliothek wurde mit einer neueren Version von YACReaderLibrary erstellt. Die neue Version jetzt herunterladen? - + Library '%1' is no longer available. Do you want to remove it? Bibliothek '%1' ist nicht mehr verfügbar. Wollen Sie sie entfernen? - - Do you want remove - Möchten Sie entfernen + + Do you want to remove the library '%1'? + Möchten Sie die Bibliothek '%1' entfernen? - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - Bibliothek '%1' wurde mit einer älteren Version von YACReader erstellt. Sie muss neu erzeugt werden. Wollen Sie die Bibliothek jetzt erzeugen? + Die Bibliothek '%1' wurde mit einer älteren Version von YACReaderLibrary erstellt. Sie muss neu erstellt werden. Möchten Sie die Bibliothek jetzt erstellen? - + Library not available Bibliothek nicht verfügbar - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. Es gab ein Problem beim Löschen der ausgewählten Comics. Überprüfen Sie bitte die Schreibberechtigung für die ausgewählten Dateien oder Ordner. - + YACReader Library - YACReader Bibliothek + YACReader-Bibliothek - + Update needed - Update benötigt + Aktualisierung erforderlich - + Library name already exists - Bibliothek-Name bereits vorhanden + Bibliotheksname bereits vorhanden - + There is another library with the name '%1'. Es gibt bereits eine Bibliothek mit dem Namen '%1'. - + Download new version Neue Version herunterladen - + Delete comics Comics löschen - + All the selected comics will be deleted from your disk. Are you sure? Alle ausgewählten Comics werden von Ihrer Festplatte gelöscht. Sind Sie sicher? - + Library not found Bibliothek nicht gefunden - + Unable to delete Löschen nicht möglich - - library? - Bibliothek? - - - + Are you sure? Sind Sie sicher? + Delete folder Ordner löschen - + Upgrade failed - Update gescheitert + Upgrade fehlgeschlagen - + There were errors during library upgrade in: Beim Upgrade der Bibliothek kam es zu Fehlern in: - + Copying comics... - Kopieren von Comics... + Comics werden kopiert... - + Moving comics... - Verschieben von Comics... + Comics werden verschoben... Folder name: - Ordnername + Ordnername: @@ -1175,7 +1908,7 @@ - Please, select a folder first + Please select a folder first Bitte wählen Sie zuerst einen Ordner aus @@ -1191,86 +1924,115 @@ The selected folder and all its contents will be deleted from your disk. Are you sure? - Der ausgewählte Ordner und sein gesamter Inhalt wird von Ihrer Festplatte gelöscht. Sind Sie sicher? + Der ausgewählte Ordner und sein gesamter Inhalt werden von Ihrer Festplatte gelöscht. Sind Sie sicher? - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - Beim Löschen des ausgewählten Ordners ist ein Problem aufgetreten. Bitte überprüfen Sie die Schreibrechte und stellen Sie sicher, dass keine Anwendung diese Ordner oder die darin enthaltenen Dateien verwendet. + Beim Löschen der ausgewählten Ordner ist ein Problem aufgetreten. Bitte überprüfen Sie die Schreibrechte und stellen Sie sicher, dass keine Anwendung diese Ordner oder die darin enthaltenen Dateien verwendet. + + + + Edit smart list... + Intelligente Liste bearbeiten... - + + Delete smart list + Intelligente Liste löschen + + + + + + New folder... + Neuer Ordner... + + + + + + Move to folder... + In Ordner verschieben... + + + + Rename folder... + Ordner umbenennen... + + + + Match to library comics... + Bibliothekscomics zuordnen... + + + + Track missing issues... + Fehlende Ausgaben erfassen... + + + + Export reading list... + Leseliste exportieren... + + + Rename or organize files Dateien umbenennen oder organisieren - + Set the type of the selected comics Typ der ausgewählten Comics festlegen - + + Save as smart list… + Als intelligente Liste speichern… + + + Search filters Suchfilter - + Unread Ungelesen - + In progress - In Bearbeitung + Begonnen - + Highly rated Hoch bewertet - + Recently added Kürzlich hinzugefügt - + Search syntax… Suchsyntax… - - A repair of this library is already running (%1). Wait for it to finish. - Für diese Bibliothek läuft bereits eine Reparatur (%1). Warten Sie, bis sie abgeschlossen ist. - - - - The library is locked by a repair that did not finish. - Die Bibliothek ist durch eine nicht abgeschlossene Reparatur gesperrt. - - - - The library is locked by a repair started by %1. - Die Bibliothek ist durch eine von %1 gestartete Reparatur gesperrt. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Wenn Sie sicher sind, dass keine andere Reparatur läuft, kann die Sperre entfernt werden. Sperre entfernen und fortfahren? - - - + Package operation failed Paketvorgang fehlgeschlagen - + The covers package operation could not be completed. Der Vorgang mit dem Cover-Paket konnte nicht abgeschlossen werden. - + Restore recovery failed Wiederherstellung nach Abbruch fehlgeschlagen @@ -1299,7 +2061,7 @@ A file or folder named '%1' already exists. - Eine Datei oder ein Ordner mit dem Namen „%1“ existiert bereits. + Eine Datei oder ein Ordner mit dem Namen '%1' existiert bereits. @@ -1321,71 +2083,65 @@ Ordner: %1 Die Datenbank der Bibliothek konnte nicht aktualisiert werden, und die Umbenennung des Ordners auf dem Datenträger konnte nicht rückgängig gemacht werden. Die Bibliothek muss jetzt manuell aktualisiert werden. - + Save covers - Titelbilder speichern + Cover speichern - + You are adding too many libraries. Sie fügen zu viele Bibliotheken hinzu. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. Sie fügen zu viele Bibliotheken hinzu. -Wahrscheinlich brauchen Sie nur eine Bibliothek in Ihrem obersten Comic-Ordner, Sie können alle Unterordner mit Hilfe des Ordnerbereichs in der linken Seitenleiste durchsuchen. +Wahrscheinlich brauchen Sie nur eine Bibliothek in Ihrem obersten Comic-Ordner. Sie können alle Unterordner mit Hilfe des Ordnerbereichs in der linken Seitenleiste durchsuchen. YACReaderLibrary wird Sie nicht daran hindern, weitere Bibliotheken zu erstellen, aber Sie sollten die Anzahl der Bibliotheken gering halten. - - + + YACReader library database (*.ydb) YACReader-Bibliotheksdatenbank (*.ydb) - + The library database backup was created at: %1 Die Sicherung der Bibliotheksdatenbank wurde hier erstellt: %1 - + Unable to create the library database backup: %1 Die Sicherung der Bibliotheksdatenbank konnte nicht erstellt werden: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? Schließen Sie vor der Wiederherstellung YACReaderLibraryServer und alle anderen YACReader-Anwendungen, die diese Bibliothek verwenden. Fortfahren? - + Restoring library database... Bibliotheksdatenbank wird wiederhergestellt... - + The current library database is invalid. Restore the selected backup anyway? Die aktuelle Bibliotheksdatenbank ist ungültig. Die ausgewählte Sicherung trotzdem wiederherstellen? - - - The library maintenance lock may be stale. Remove it and retry? - Die Wartungssperre der Bibliothek ist möglicherweise veraltet. Entfernen und erneut versuchen? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1394,71 +2150,77 @@ Restart YACReaderLibrary before attempting recovery again. Starten Sie YACReaderLibrary neu, bevor Sie erneut eine Wiederherstellung versuchen. - + The library database was restored successfully. Update the library now? Die Bibliotheksdatenbank wurde erfolgreich wiederhergestellt. Bibliothek jetzt aktualisieren? - + Library database damaged Bibliotheksdatenbank beschädigt - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. Die Datenbank der Bibliothek '%1' ist beschädigt, daher sind normale Aktualisierungen, Wartungsarbeiten und Sicherungen nicht verfügbar. YACReader kann versuchen, die Datenbank zu reparieren. Einige beschädigte Daten können möglicherweise nicht wiederhergestellt werden. Vorhandene Sicherungen werden nicht verändert. - + Attempt repair Reparatur versuchen - + Restore a backup... Sicherung wiederherstellen... - + Repairing library database... Bibliotheksdatenbank wird repariert... - - + + + Library database repair Reparatur der Bibliotheksdatenbank - - Another maintenance operation is currently using this library. Try again after it finishes. - Ein anderer Wartungsvorgang verwendet diese Bibliothek derzeit. Versuchen Sie es nach dessen Abschluss erneut. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Die Datenbank der Bibliothek '%1' prüfen und bei Bedarf eine Reparatur versuchen? Vorhandene Sicherungen werden nicht geändert. + + + + Check and repair + Prüfen und reparieren - + The library database is already valid. Die Bibliotheksdatenbank ist bereits gültig. - + Library database repaired Bibliotheksdatenbank repariert - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 Die Bibliotheksdatenbank wurde durch den Neuaufbau ihrer Indizes repariert. Das beschädigte Original wurde hier aufbewahrt: %1 - + Library database rebuilt Bibliotheksdatenbank neu aufgebaut - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1469,7 +2231,7 @@ Update the library now? Bibliothek jetzt aktualisieren? - + The damaged original was preserved at: @@ -1480,12 +2242,12 @@ Das beschädigte Original wurde hier aufbewahrt: %1 - + Library database repair failed Reparatur der Bibliotheksdatenbank fehlgeschlagen - + The library database could not be repaired: %1%2 @@ -1496,61 +2258,102 @@ You can restore a backup from the Library menu or recreate the library. Sie können über das Bibliotheksmenü eine Sicherung wiederherstellen oder die Bibliothek neu erstellen. - + Remove and delete metadata and backups Metadaten und Sicherungen entfernen und löschen - + Library info Informationen zur Bibliothek - + Assign comics numbers Comics Nummern zuweisen - - Assign numbers starting in: + + Assign numbers starting at: Nummern zuweisen, beginnend mit: - + Invalid image Ungültiges Bild - + The selected file is not a valid image. Die ausgewählte Datei ist kein gültiges Bild. - + + Error saving cover Fehler beim Speichern des Covers - + + There was an error saving the cover image. - Beim Speichern des Titelbildes ist ein Fehler aufgetreten. + Beim Speichern des Coverbildes ist ein Fehler aufgetreten. + + + + + Match to library comic + Bibliothekscomic zuordnen + + + + This also gives the comic to entries in %n other list(s): %1 + + Dadurch wird der Comic auch Einträgen in %n weiterer Liste zugeordnet: %1 + Dadurch wird der Comic auch Einträgen in %n weiteren Listen zugeordnet: %1 + - + + The comic could not be matched. Nothing was changed. + Der Comic konnte nicht zugeordnet werden. Es wurde nichts geändert. + + + + The selected missing comics will be removed from this folder. Are you sure? + Die ausgewählten fehlenden Comics werden aus diesem Ordner entfernt. Sind Sie sicher? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + Die ausgewählten Comics werden von Ihrem Datenträger gelöscht und die ausgewählten fehlenden Comics aus diesem Ordner entfernt. Sind Sie sicher? + + + + Remove the comics from Reading? Their reading progress will be reset. + Die Comics aus „Wird gelesen“ entfernen? Ihr Lesefortschritt wird zurückgesetzt. + + + + The comics will be removed from Favorites. Are you sure? + Die Comics werden aus den Favoriten entfernt. Sind Sie sicher? + + + Remove comics - Comics löschen + Comics entfernen - + Comics will only be deleted from the current label/list. Are you sure? Comics werden nur vom aktuellen Label/der aktuellen Liste gelöscht. Sind Sie sicher? - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1569,34 +2372,34 @@ Fehlende Dateien: %3 Open an existing library - Eine vorhandede Bibliothek öffnen + Eine vorhandene Bibliothek öffnen Export comics info - Comicinfo exportieren + Comic-Infos exportieren Import comics info - Importiere Comic-Info + Comic-Infos importieren Pack covers - Titelbild-Paket erzeugen + Cover-Paket erstellen Pack the covers of the selected library - Packe die Titelbilder der ausgewählten Bibliothek in ein Paket + Cover-Paket der ausgewählten Bibliothek erstellen Unpack covers - Titelbilder entpacken + Cover entpacken @@ -1606,12 +2409,12 @@ Fehlende Dateien: %3 Update library - Bibliothek updaten + Bibliothek aktualisieren Update current library - Aktuelle Bibliothek updaten + Aktuelle Bibliothek aktualisieren @@ -1635,433 +2438,504 @@ Fehlende Dateien: %3 + Repair library database... + Bibliotheksdatenbank reparieren... + + + + Check the current library database and attempt to repair any damage + Die aktuelle Bibliotheksdatenbank prüfen und versuchen, Schäden zu reparieren + + + + Recover library access... + Bibliothekszugriff wiederherstellen... + + + + Recover access after an interrupted library maintenance operation + Zugriff nach einer unterbrochenen Bibliothekswartung wiederherstellen + + + Repair covers and comic info Cover und Comic-Informationen reparieren - + Retry comics with missing covers or incomplete information Comics mit fehlenden Covern oder unvollständigen Informationen erneut verarbeiten - + Rename library Bibliothek umbenennen - + Rename current library Aktuelle Bibliothek umbenennen - + Remove library Bibliothek entfernen - + Remove current library from your collection Aktuelle Bibliothek aus der Sammlung entfernen - + Rescan library for XML info - Durchsuchen Sie die Bibliothek erneut nach XML-Informationen + Bibliothek erneut nach XML-Informationen durchsuchen - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Versucht, in Comic-Dateien eingebettete XML-Informationen zu finden. Sie müssen dies nur tun, wenn die Bibliothek mit 9.8.2 oder früheren Versionen erstellt wurde oder wenn Sie Software von Drittanbietern verwenden, um XML-Informationen in die Dateien einzubetten. - + Open library folder... Bibliotheksordner öffnen... - + Open the root folder of the current library Stammordner der aktuellen Bibliothek öffnen - + Show library info Bibliotheksinformationen anzeigen - + Show information about the current library Informationen zur aktuellen Bibliothek anzeigen - + Open current comic Aktuellen Comic öffnen - - Open current comic on YACReader + + Open current comic in YACReader Aktuellen Comic mit YACReader öffnen - + Save selected covers to... - Ausgewählte Titelbilder speichern in... + Ausgewählte Cover speichern in... - + Save covers of the selected comics as JPG files - Titelbilder der ausgewählten Comics als JPG-Datei speichern + Cover der ausgewählten Comics als JPG-Dateien speichern - - + + Set as read Als gelesen markieren - + Set comic as read Comic als gelesen markieren - - + + Set as unread Als ungelesen markieren - + Set comic as unread Comic als ungelesen markieren - - + + manga Manga - + Set issue as manga Ausgabe als Manga festlegen - - + + comic - komisch + Comic - + Set issue as normal Ausgabe als normal festlegen - + western manga Western-Manga - + Set issue as western manga Ausgabe als Western-Manga festlegen - - + + web comic Webcomic - + Set issue as web comic Ausgabe als Webcomic festlegen - - + + yonkoma Yonkoma - + Set issue as yonkoma - Stellen Sie das Problem als Yonkoma ein + Ausgabe als Yonkoma festlegen - + Show/Hide marks - Zeige/Verberge Markierungen + Markierungen anzeigen/verbergen - + Show or hide read marks Gelesen-Markierungen anzeigen oder verbergen - + Show/Hide recent indicator - Aktuelle Anzeige ein-/ausblenden + Anzeige für kürzlich hinzugefügte Comics ein-/ausblenden - + Show or hide recent indicator - Aktuelle Anzeige anzeigen oder ausblenden + Anzeige für kürzlich hinzugefügte Comics anzeigen oder ausblenden - - + + Fullscreen mode on/off Vollbildmodus an/aus - + Help, About YACReader Hilfe, Über YACReader - + Add new folder Neuen Ordner erstellen - + Add new folder to the current library Neuen Ordner in der aktuellen Bibliothek erstellen - + Rename folder Ordner umbenennen - + Rename the current folder on disk and in the library Den aktuellen Ordner auf dem Datenträger und in der Bibliothek umbenennen - + Delete folder Ordner löschen - + Delete current folder from disk Aktuellen Ordner von der Festplatte löschen - + Select root node - Ursprungsordner auswählen + Stammordner auswählen - + Expand all nodes - Alle Unterordner anzeigen + Alle Knoten erweitern - + Collapse all nodes - Alle Unterordner einklappen + Alle Knoten einklappen - + Show options dialog - Zeige den Optionen-Dialog + Optionsdialog anzeigen - + Show comics server options dialog - Zeige Comic-Server-Optionen-Dialog + Optionsdialog des Comic-Servers anzeigen - - + + Change between comics views Zwischen Comic-Anzeigemodi wechseln - + + Grid view + Rasteransicht + + + + Flow view + + + + + Info view + + + + Open folder... - Öffne Ordner... + Ordner öffnen... - - + + Rename files... Organize files Dateien umbenennen... - - + + Organize into folders... In Ordner organisieren... - + Set as uncompleted - Als nicht gelesen markieren + Als unvollständig markieren - + Set as completed - Als gelesen markieren + Als abgeschlossen markieren - + Set custom cover - Legen Sie ein benutzerdefiniertes Cover fest + Benutzerdefiniertes Cover festlegen - + Delete custom cover Benutzerdefiniertes Cover löschen - + western manga (left to right) Western-Manga (von links nach rechts) - + Open containing folder... - Öffne aktuellen Ordner... + Zugehörigen Ordner öffnen... - Reset comic rating - Comic-Bewertung zurücksetzen + + New reading list + Neue Leseliste - + Select all comics Alle Comics auswählen - + Edit Bearbeiten - + Assign current order to comics - Aktuele Sortierung auf Comics anwenden + Aktuelle Sortierung auf Comics anwenden - + Update cover - Titelbild updaten + Cover aktualisieren - + Delete selected comics Ausgewählte Comics löschen - + Delete metadata from selected comics Metadaten aus ausgewählten Comics löschen - + Download tags from Comic Vine Tags von Comic Vine herunterladen - + Focus search line Suchzeile fokussieren - + Focus comics view - Fokus-Comic-Ansicht + Comic-Ansicht fokussieren - + Edit shortcuts Kürzel bearbeiten - + &Quit - &Schließen + &Beenden - + Update folder Ordner aktualisieren - + Update current folder Aktuellen Ordner aktualisieren - + Scan legacy XML metadata - Scannen Sie ältere XML-Metadaten + Ältere XML-Metadaten scannen - - Add new reading list - Neue Leseliste hinzufügen - - - + Add a new reading list to the current library Neue Leseliste zur aktuellen Bibliothek hinzufügen - + + New smart list... + Neue intelligente Liste... + + + + Add a saved search to the current library + Eine gespeicherte Suche zur aktuellen Bibliothek hinzufügen + + + + New folder... + Neuer Ordner... + + + + Add a folder for reading lists or smart lists + Einen Ordner für Leselisten oder intelligente Listen hinzufügen + + + + Import reading lists... + Leselisten importieren... + + + + Import CBL reading lists into the current library + CBL-Leselisten in die aktuelle Bibliothek importieren + + + + New + Neu + + + + Add or import reading lists + Leselisten hinzufügen oder importieren + + + Remove reading list Leseliste entfernen - + Remove current reading list from the library Aktuelle Leseliste von der Bibliothek entfernen - + Add new label Neues Label hinzufügen - + Add a new label to this library Neues Label zu dieser Bibliothek hinzufügen - + Rename selected list Ausgewählte Liste umbenennen - + Rename any selected labels or lists Ausgewählte Labels oder Listen umbenennen - + Add to... Hinzufügen zu... - + Favorites Favoriten - + Add selected comics to favorites list Ausgewählte Comics zu Favoriten hinzufügen - - + + Reset rating Bewertung zurücksetzen @@ -2071,7 +2945,7 @@ Fehlende Dateien: %3 comic - komisch + Comic @@ -2090,91 +2964,104 @@ Fehlende Dateien: %3 - 4koma (top to botom) + 4koma (top to bottom) 4koma (von oben nach unten) - - - + + + Set type Typ festlegen - + Library Bibliothek - + Folder Ordner - - Comic - Comic + + Comic + Comic + + + + Match to library comic... + Bibliothekscomic zuordnen... + + + + + + + Track missing issues... + Fehlende Ausgaben erfassen... - + Open folder... - Öffne Ordner... + Ordner öffnen... - + Update folder Ordner aktualisieren - + Rename folder Ordner umbenennen - + Rename files... Dateien umbenennen... - + Organize into folders... In Ordner organisieren... - + Rescan library for XML info - Durchsuchen Sie die Bibliothek erneut nach XML-Informationen + Bibliothek erneut nach XML-Informationen durchsuchen - + Set as uncompleted - Als nicht gelesen markieren + Als unvollständig markieren - + Set as completed - Als gelesen markieren + Als abgeschlossen markieren - + Set as read Als gelesen markieren - - + + Set as unread Als ungelesen markieren - + Set custom cover - Legen Sie ein benutzerdefiniertes Cover fest + Benutzerdefiniertes Cover festlegen - - + + Delete custom cover Benutzerdefiniertes Cover löschen @@ -2215,11 +3102,57 @@ Fehlende Dateien: %3 LocalComicListModel - + file name Dateiname + + MoveToFolderDialog + + + Move to folder + In Ordner verschieben + + + + Top level + Oberste Ebene + + + + Move "%1" to: + „%1“ verschieben nach: + + + + NewListFolderDialog + + + New folder + Neuer Ordner + + + + Reading lists + Leselisten + + + + Smart lists + Intelligente Listen + + + + Folder name: + Ordnername: + + + + For: + Für: + + NoLibrariesWidget @@ -2234,13 +3167,13 @@ Fehlende Dateien: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>Sie können eine Bibliothek in jedem beliebigen Ordner erstellen, YACReaderLibrary wird alle Comics und Unterordner von diesem Ordner importieren. Wenn Sie bereits eine Bibliothek erstellt haben, können Sie sie öffnen.</p><p>Vergessen Sie nicht, dass Sie YACReader als eigentsändige Anwendung nutzen können, um Comics auf Ihrem Computer zu lesen.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Sie können eine Bibliothek in jedem beliebigen Ordner erstellen. YACReaderLibrary importiert alle Comics und Unterordner aus diesem Ordner. Wenn Sie bereits Bibliotheken erstellt haben, können Sie diese öffnen.</p><p>Vergessen Sie nicht, dass Sie YACReader auch als eigenständige Anwendung zum Lesen von Comics auf Ihrem Computer nutzen können.</p> add an existing one - Existierende hinzufügen + Vorhandene hinzufügen @@ -2281,17 +3214,17 @@ Fehlende Dateien: %3 Tray icon settings (experimental) - Taskleisten-Einstellungen (experimentell) + Einstellungen für das Systemtray-Symbol (experimentell) Close to tray - In Taskleiste schließen + In den Systemtray schließen - Start into the system tray - In die Taskleiste starten + Start in the system tray + Im Systemtray starten @@ -2301,23 +3234,23 @@ Fehlende Dateien: %3 Comic Vine API key - Comic Vine API Schlüssel + Comic Vine API-Schlüssel ComicInfo.xml legacy support - ComicInfo.xml-Legacy-Unterstützung + Unterstützung für ältere ComicInfo.xml-Dateien Import metadata from ComicInfo.xml when adding new comics Import metada from ComicInfo.xml when adding new comics - Importieren Sie Metadaten aus ComicInfo.xml, wenn Sie neue Comics hinzufügen + Metadaten aus ComicInfo.xml beim Hinzufügen neuer Comics importieren Consider 'recent' items added or updated since X days ago - Berücksichtigen Sie „neue“ Elemente, die seit X Tagen hinzugefügt oder aktualisiert wurden + Elemente als „neu“ betrachten, die seit X Tagen hinzugefügt oder aktualisiert wurden @@ -2327,7 +3260,7 @@ Fehlende Dateien: %3 Write {comic_file_path} where the path should go in the command - Schreiben Sie {comic_file_path}, wohin der Pfad im Befehl gehen soll + Schreiben Sie {comic_file_path} an die Stelle im Befehl, an der der Pfad stehen soll @@ -2337,17 +3270,17 @@ Fehlende Dateien: %3 Update libraries at startup - Aktualisieren Sie die Bibliotheken beim Start + Bibliotheken beim Start aktualisieren Try to detect changes automatically - Versuchen Sie, Änderungen automatisch zu erkennen + Änderungen automatisch erkennen Update libraries periodically - Aktualisieren Sie die Bibliotheken regelmäßig + Bibliotheken regelmäßig aktualisieren @@ -2392,19 +3325,19 @@ Fehlende Dateien: %3 Update libraries at certain time - Aktualisieren Sie Bibliotheken zu einem bestimmten Zeitpunkt + Bibliotheken zu einer bestimmten Uhrzeit aktualisieren Time: - Zeit: + Uhrzeit: - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2421,7 +3354,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Compare the modified date of files when updating a library (not recommended) - Vergleichen Sie das Änderungsdatum von Dateien beim Aktualisieren einer Bibliothek (nicht empfohlen) + Änderungsdatum von Dateien beim Aktualisieren einer Bibliothek vergleichen (nicht empfohlen) @@ -2441,11 +3374,11 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Use selected comic cover as background - Den ausgewählten Comic als Hintergrund verwenden + Cover des ausgewählten Comics als Hintergrund verwenden - Restore defautls + Restore defaults Standardwerte wiederherstellen @@ -2513,12 +3446,93 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n OrganizeFiles - + + + Unknown Series + Unbekannte Serie + + + + series + Serie + + + + Unknown Publisher + Unbekannter Verlag + + + + publisher + Verlag + + + + title + Titel + + + + Unknown Comic + Unbekannter Comic + + + + Series #Number - Title + Serie #Nummer - Titel + + + + Series #Number + Serie #Nummer + + + + Series #Number (of Count) + Serie #Nummer (von Gesamtanzahl) + + + + Number - Title + Nummer - Titel + + + + Series #Number (Year) + Serie #Nummer (Jahr) + + + + Publisher / Series / Series #Number - Title + Verlag / Serie / Serie #Nummer - Titel + + + + Publisher / Imprint / Series / Series #Number - Title + Verlag / Imprint / Serie / Serie #Nummer - Titel + + + + Series / Series #Number + Serie / Serie #Nummer + + + + Publisher / Series / Series #Number (Year) + Verlag / Serie / Serie #Nummer (Jahr) + + + + Series / original file name + Serie / ursprünglicher Dateiname + + + Renamed, %1 is already in use Umbenannt, %1 wird bereits verwendet - + Missing metadata: %1 Fehlende Metadaten: %1 @@ -2527,62 +3541,82 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n %1 could not be created %1 konnte nicht erstellt werden + + + %1 (copy also failed: %2) + %1 (auch das Kopieren ist fehlgeschlagen: %2) + + + + the copy did not have the same size as the original + die Kopie hatte nicht dieselbe Größe wie das Original + + + + the original could not be deleted after it was copied + das Original konnte nach dem Kopieren nicht gelöscht werden + + + + The destination folder could not be created. + Der Zielordner konnte nicht erstellt werden. + + + + %1 (the file was left at %2) + %1 (die Datei wurde unter %2 belassen) + OrganizeFilesCoordinator - - + + Organize files Dateien organisieren - + This folder does not contain any comics. Dieser Ordner enthält keine Comics. - - This library is busy: %1 - Diese Bibliothek ist belegt: %1 - - - + the library database could not be opened die Datenbank der Bibliothek konnte nicht geöffnet werden - + the library database could not be locked for writing die Datenbank der Bibliothek konnte nicht zum Schreiben gesperrt werden - + a folder entry could not be restored ein Ordnereintrag konnte nicht wiederhergestellt werden - + a comic entry could not be updated ein Comic-Eintrag konnte nicht aktualisiert werden - + the library database could not be saved: %1 die Datenbank der Bibliothek konnte nicht gespeichert werden: %1 - + the record of the last organize run could not be read die Aufzeichnung des letzten Organisierens konnte nicht gelesen werden - + the folder %1 could not be created der Ordner %1 konnte nicht erstellt werden - + %n file(s) could not be moved back %n Datei konnte nicht zurückverschoben werden @@ -2592,10 +3626,6 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n OrganizeFilesDialog - - Format: - Formatangabe: - Organize files @@ -2670,7 +3700,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n library root - Wurzel der Bibliothek + Stammordner der Bibliothek @@ -2727,19 +3757,11 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Cancel Abbrechen - - Copy the list - Liste kopieren - Undo Rückgängig - - Close - Schließen - Copy failure details @@ -3158,276 +4180,388 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n PropertiesDialog - + Day: Tag: - + Plot - Inhalt + Handlung - + Size: Größe: - + Year: Jahr: - + Inker(s): - Künstler(Tinte): + Tuscher: - + Publishing Veröffentlichung - + Publisher: Verlag: - + General info Allgemeine Info - + Color/BW: Farbe/Schwarz-Weiß: - + Edit selected comics information Ausgewählte Comic-Informationen bearbeiten - + Penciller(s): - Künstler(Bleistift): + Zeichner: - + Colorist(s): - Künstler(Farbe): + Kolorist(en): - + Genre: - Gattung: + Genre: - + Notes Notizen - + Load previous page as cover Vorherige Seite als Cover laden - + Load next page as cover Nächste Seite als Cover laden - + Reset cover to the default image Cover auf das Standardbild zurücksetzen - + Load custom cover image - Laden Sie ein benutzerdefiniertes Titelbild + Benutzerdefiniertes Cover laden - + Series: Serie: - + + e.g. 1-5, 8, 0.5, Annual 1 + z. B. 1-5, 8, 0.5, Annual 1 + + + + Issue number: Ausgabennummer: - + alt. number: alt. Nummer: - + Alternate series: Alternative Serie: - + Series Group: Seriengruppe: - + Editor(s): - Herausgeber(n): + Lektor(en): - + Imprint: - Impressum: + Imprint: - + Month: Monat: - + Type: Typ: - + Language (ISO): Sprache (ISO): - + Teams: - Mannschaften: + Teams: - + Locations: - Standorte: + Schauplätze: - + Main character or team: Hauptfigur oder Team: - + Review: Rezension: - + Notes: - Anmerkungen: + Notizen: + + + + + Track missing issues in "%1" + Fehlende Ausgaben in „%1“ erfassen + + + + Issues: + Ausgaben: + + + + Track + Erfassen + + + + At most %1 issues can be tracked at once. + Es können höchstens %1 Ausgaben auf einmal erfasst werden. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Prüfen Sie die Ausgaben: Bereiche müssen aufsteigend sein und zwischen Kommas darf kein leerer Eintrag stehen. + + + + No new issues to track. + Keine neuen Ausgaben zu erfassen. + + + + %n issue(s) will be tracked: %1. + + %n Ausgabe wird erfasst: %1. + %n Ausgaben werden erfasst: %1. + + + + + That is a lot of issues; check the numbers. + Das sind viele Ausgaben. Prüfen Sie die Nummern. + + + + %n issue(s) already in the folder. + + %n Ausgabe ist bereits im Ordner. + %n Ausgaben sind bereits im Ordner. + + + + + %n repeated issue(s). + + %n doppelte Ausgabe. + %n doppelte Ausgaben. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Nicht erfasste Ausgaben + + + + The missing issues could not be tracked. + Die fehlenden Ausgaben konnten nicht erfasst werden. + + + + Comic skipped + Comic übersprungen + + + + A comic was removed from the library or now has a different file, so it is skipped. + Ein Comic wurde aus der Bibliothek entfernt oder hat jetzt eine andere Datei und wird daher übersprungen. + + + + + Changes not saved + Änderungen nicht gespeichert + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Ein Comic, den Sie bearbeiten, wurde aus der Bibliothek entfernt oder hat jetzt eine andere Datei. Ihre Änderungen wurden nicht gespeichert. + + + + The changes could not be saved. + Die Änderungen konnten nicht gespeichert werden. - + Invalid cover - Ungültiger Versicherungsschutz + Ungültiges Cover - + The image is invalid. Das Bild ist ungültig. - + Synopsis: Zusammenfassung: - + Title: Titel: - + Not found Nicht gefunden - + Characters: - Charaktere: + Figuren: - + Authors Autoren - + Age rating: - Altersangabe: + Altersfreigabe: - + Story arc: - Handlung: + Handlungsbogen: - + Writer(s): Autor(en): - + Tags: - Schlagworte: + Tags: - + Comic not found. You should update your library. - Comic nicht gefunden. Sie sollten Ihre Bibliothek updaten. + Comic nicht gefunden. Sie sollten Ihre Bibliothek aktualisieren. - + Edit comic information Comic-Informationen bearbeiten - + Cover page - Titelbild + Cover - + Cover Artist(s): - Titelbild-Künstler: + Coverzeichner: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> Comic Vine-Link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> ansehen </a> - + Volume: Band: - + Format: - Formatangabe: + Format: - + Letterer(s): - Künstler(Schrift): + Letterer: - - + + of: - von + von: - + Arc number: - Handlungsbogen Nummer: + Nummer des Handlungsbogens: @@ -3435,20 +4569,20 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n 7z lib not found - 7z Bibliothek nicht gefunden + 7z-Bibliothek nicht gefunden unable to load 7z lib from ./utils - 7z Bibliothek kann von ./utils nicht geladen werden + 7z-Bibliothek kann nicht aus ./utils geladen werden - + Select custom cover Wählen Sie ein benutzerdefiniertes Cover - + Images (%1) Bilder (%1) @@ -3460,7 +4594,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n This theme is for %1, not %2. - Dieses Thema ist für %1, nicht für %2. + Dieses Design ist für %1, nicht für %2. @@ -3481,31 +4615,166 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n ReadingListManagementCoordinator - + Add new reading lists - Neue Leseliste hinzufügen + Neue Leselisten hinzufügen - - + + List name: - Name der Liste + Name der Liste: + + + + Import reading lists + Leselisten importieren + + + + Reading lists (*.cbl *.json);;All files (*) + Leselisten (*.cbl *.json);;Alle Dateien (*) + + + + New smart list + Neue intelligente Liste + + + + + The smart list could not be saved. + Die intelligente Liste konnte nicht gespeichert werden. + + + + Edit smart list + Intelligente Liste bearbeiten + + + + Delete folder + Ordner löschen + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Den Ordner „%1“ und seinen gesamten Inhalt löschen? Seine Unterordner und Listen werden gelöscht, Ihre Comicdateien bleiben erhalten. - + Delete list/label - Ausgewählte/s Liste/Label löschen + Liste/Label löschen - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? Das ausgewählte Element wird gelöscht; Ihre Comics oder Ordner werden NICHT von Ihrer Festplatte gelöscht. Sind Sie sicher? - + Rename list name Listenname ändern + + + Rename folder + Ordner umbenennen + + + + + Folder name: + Ordnername: + + + + + + New folder + Neuer Ordner + + + + + The folder could not be created. + Der Ordner konnte nicht erstellt werden. + + + + Reading list + Leseliste + + + + CBL reading list (*.cbl) + CBL-Leseliste (*.cbl) + + + + JSON reading list (*.json) + JSON-Leseliste (*.json) + + + + Web page (*.html) + Webseite (*.html) + + + + + Export reading list + Leseliste exportieren + + + + Exporting reading list... + Leseliste wird exportiert... + + + + The file could not be written. + Die Datei konnte nicht geschrieben werden. + + + + Move to folder + In Ordner verschieben + + + + "%1" could not be moved. + „%1“ konnte nicht verschoben werden. + + + + ReadingListPage + + + %n comic(s) + + %n Comic + %n Comics + + + + + %n missing + + %n fehlt + %n fehlen + + + + + Missing + Fehlend + + + + Exported from YACReader on %1 + Am %1 aus YACReader exportiert + RenameLibraryDialog @@ -3526,16 +4795,16 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n - New Library Name : - Neuer Bibliotheksname : + New Library Name: + Neuer Bibliotheksname: ScraperResultsPaginator - Number of %1 found : %2 - Anzahl von %1 gefunden : %2 + Number of %1 found: %2 + Anzahl gefundener %1: %2 @@ -3545,7 +4814,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n - Number of volumes found : %1 + Number of volumes found: %1 Anzahl der gefundenen Bände: %1 @@ -3644,7 +4913,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Editor credit - Redakteur + Lektor @@ -3881,7 +5150,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Search every comic and folder field, or build precise queries. - Durchsuche alle Comic- und Ordnerfelder oder erstelle präzise Abfragen. + Durchsuchen Sie alle Comic- und Ordnerfelder oder erstellen Sie präzise Abfragen. @@ -3906,7 +5175,12 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Just start typing. Plain text search across all metadata. - Beginne einfach mit der Eingabe. Einfacher Text durchsucht alle Metadaten. + Beginnen Sie einfach mit der Eingabe. Einfacher Text durchsucht alle Metadaten. + + + + %1 -> search for `%1` in any database field + %1 -> nach `%1` in jedem Datenbankfeld suchen @@ -3916,7 +5190,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Type any text or quoted text. - Gib beliebigen Text oder Text in Anführungszeichen ein. + Geben Sie beliebigen Text oder Text in Anführungszeichen ein. @@ -3926,7 +5200,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Use a field name followed by : or = - Verwende einen Feldnamen gefolgt von : oder = + Verwenden Sie einen Feldnamen, gefolgt von : oder = @@ -3936,7 +5210,7 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Use AND, OR, NOT and parentheses. - Verwende AND, OR, NOT und Klammern. + Verwenden Sie AND, OR, NOT und Klammern. @@ -4003,7 +5277,9 @@ Um eine automatische Aktualisierung zu stoppen, tippen Sie auf die Ladeanzeige n Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Tipps: Leerzeichen wirken wie AND und die Suche unterscheidet nicht zwischen Groß- und Kleinschreibung. Verwende Anführungszeichen für Leerzeichen in einem Wert. + Tipps: +Leerzeichen wirken wie AND, und bei der Suche wird nicht zwischen Groß- und Kleinschreibung unterschieden. +Verwenden Sie Anführungszeichen, um Leerzeichen in einem Wert einzuschließen. @@ -4038,7 +5314,7 @@ Use quotes to include spaces in a value. Examples show the pattern—replace the values with your own. - Die Beispiele zeigen das Muster — ersetze die Werte durch deine eigenen. + Die Beispiele zeigen das Muster — ersetzen Sie die Werte durch Ihre eigenen. @@ -4133,7 +5409,7 @@ Use quotes to include spaces in a value. Spaces behave like AND. Use quotes for phrases and parentheses to control grouping. - Leerzeichen wirken wie AND. Verwende Anführungszeichen für Ausdrücke und Klammern, um die Gruppierung zu steuern. + Leerzeichen wirken wie AND. Verwenden Sie Anführungszeichen für Ausdrücke und Klammern, um die Gruppierung zu steuern. @@ -4159,7 +5435,7 @@ Use quotes to include spaces in a value. loading description - Beschreibung wird laden + Beschreibung wird geladen @@ -4169,7 +5445,7 @@ Use quotes to include spaces in a value. loading cover - Titelbild wird geladen + Cover wird geladen @@ -4178,7 +5454,7 @@ Use quotes to include spaces in a value. - Please, select the right comic info. + Please select the right comic info. Bitte wählen Sie die korrekte Comic-Information aus. @@ -4191,13 +5467,13 @@ Use quotes to include spaces in a value. - Please, select the right series for your comic. - Bitte wählen Sie die korrekte Serie für Ihre Comics aus. + Please select the right series for your comic. + Bitte wählen Sie die korrekte Serie für Ihren Comic aus. Filter: - Filteroption: + Filter: @@ -4207,7 +5483,7 @@ Use quotes to include spaces in a value. loading cover - Titelbild wird geladen + Cover wird geladen @@ -4225,54 +5501,59 @@ Use quotes to include spaces in a value. Unknown - Unbekannt + Unbekannt %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 unbekannt) %1 series - + %1 Serien - + %1 comics selected %n comic(s) selected - %1 Comic ausgewählt + %1 Comics ausgewählt - + Read - Lesen + Gelesen - + In progress - In Bearbeitung + Begonnen - + Unread - Ungelesen + Ungelesen - + Total pages - + Seiten gesamt - + Total size - + Gesamtgröße + + + + Missing + Fehlend - + Series - Serie + Serie @@ -4289,8 +5570,8 @@ Use quotes to include spaces in a value. - You are trying to get information for various comics at once, are they part of the same series? - Sie versuchen, Informationen zu mehreren Comics gleichzeitig zu laden, sind diese Teil einer Serie? + You are trying to get information for various comics at once. Are they part of the same series? + Sie versuchen, Informationen zu mehreren Comics gleichzeitig zu laden. Gehören sie zur selben Serie? @@ -4319,7 +5600,7 @@ Use quotes to include spaces in a value. Port - Anschluss + Port @@ -4346,59 +5627,75 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader ist für iOS und Android verfügbar. Entdecken Sie es für <a href='https://ios.yacreader.com'>iOS</a> oder <a href='https://android.yacreader.com'>Android</a>. - - enable the server - Server aktivieren - Set port set port Port festlegen + + + SmartListDialog + + + New smart list + Neue intelligente Liste + - Server connectivity information - Serveranschluss-Information + + Edit smart list + Intelligente Liste bearbeiten - Scan it! - Durchsuchen! + + Search filters and syntax + Suchfilter und Syntax - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader ist für iOS- und Android-Geräte verfügbar.<br/>Entdecken Sie es für <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> oder <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Name: + Name: - Choose an IP address - IP-Adresse auswählen + + Search: + Suche: SortVolumeComics - + remove selected comics Ausgewählte Comics entfernen - + sort comics to match comic information Comics laut Comic-Information sortieren - + restore all removed comics Alle entfernten Comics wiederherstellen + + + Add the %n other issue(s) of this volume as missing comics + + Die %n weitere Ausgabe dieses Bandes als fehlenden Comic hinzufügen + Die %n weiteren Ausgaben dieses Bandes als fehlende Comics hinzufügen + + - + issues Ausgaben - - Please, sort the list of comics on the left until it matches the comics' information. - Sortieren Sie bitte die Comic-Informationen links, bis die Informationen mit den Comics übereinstimmen. + + Please sort the list of comics on the left until it matches the comics' information. + Sortieren Sie bitte die Comic-Liste links, bis sie mit den Comic-Informationen übereinstimmt. @@ -4406,7 +5703,7 @@ Use quotes to include spaces in a value. Theme Editor - Theme-Editor + Design-Editor @@ -4421,7 +5718,7 @@ Use quotes to include spaces in a value. i - ich + i @@ -4436,7 +5733,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Halten Sie gedrückt, um den ausgewählten Wert in der Benutzeroberfläche zu blinken (Magenta / umgeschaltet / 0↔10). Loslassen stellt das Original wieder her. + Halten Sie gedrückt, um den ausgewählten Wert in der Benutzeroberfläche blinken zu lassen (Magenta / umgeschaltet / 0↔10). Loslassen stellt das Original wieder her. @@ -4446,7 +5743,7 @@ Use quotes to include spaces in a value. Light - Helligkeit + Hell @@ -4456,7 +5753,7 @@ Use quotes to include spaces in a value. ID: - AUSWEIS: + ID: @@ -4471,12 +5768,12 @@ Use quotes to include spaces in a value. Theme info - Themeninfo + Design-Info Parameter - Parameterwert + Parameter @@ -4516,7 +5813,7 @@ Use quotes to include spaces in a value. true - WAHR + true @@ -4524,7 +5821,7 @@ Use quotes to include spaces in a value. false - FALSCH + false @@ -4546,7 +5843,7 @@ Use quotes to include spaces in a value. Save theme - Thema speichern + Design speichern @@ -4569,7 +5866,7 @@ Use quotes to include spaces in a value. Load theme - Theme laden + Design laden @@ -4589,7 +5886,7 @@ Use quotes to include spaces in a value. Invalid JSON: %1 - Ungültiger JSON: + Ungültiges JSON: %1 @@ -4603,7 +5900,7 @@ Use quotes to include spaces in a value. SEARCH - Suchen + SUCHEN @@ -4620,8 +5917,16 @@ Use quotes to include spaces in a value. - Updating.... - Aktualisierung.... + Updating... + Aktualisierung... + + + + ViewSelector + + + View + @@ -4647,7 +5952,7 @@ Use quotes to include spaces in a value. publisher - Herausgeber + Verlag @@ -4680,12 +5985,12 @@ Use quotes to include spaces in a value. Roulette look - Zufällige Darstellung + Roulette-Ansicht Show advanced settings - Zeige erweiterte Einstellungen + Erweiterte Einstellungen anzeigen @@ -4695,42 +6000,42 @@ Use quotes to include spaces in a value. View angle - Zeige Winkel + Blickwinkel Position - Lage + Position Cover gap - Titelbild Abstand + Cover-Abstand Central gap - Mittiger Abstand + Mittlerer Abstand Zoom - Vergößern + Zoom Y offset - Y Abstand + Y-Versatz Z offset - Z Abstand + Z-Versatz Cover Angle - Titelbild-Winkel + Cover-Winkel @@ -4740,7 +6045,7 @@ Use quotes to include spaces in a value. Light - Helligkeit + Licht @@ -4759,8 +6064,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - VSync nutzen (verbessert die Bilddarstellung im Vollbild, schlechtere Leistung) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + VSync verwenden (verbessert die Bildqualität im Vollbildmodus, schlechtere Leistung) @@ -4778,12 +6083,12 @@ Use quotes to include spaces in a value. Systray - Taskleiste + Systemtray YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary wird im Hintergrund weiterlaufen. Um das Programm zu schließen, wählen Sie <b>Schließen</b> im Kontextmenü des Taskleisten-Symbols. + YACReaderLibrary wird im Systemtray weiterlaufen. Zum Beenden des Programms wählen Sie <b>Beenden</b> im Kontextmenü des Systemtray-Symbols. @@ -4799,16 +6104,54 @@ Use quotes to include spaces in a value. Frühere Versionen + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Die Bibliotheksdatenbank fehlt. Stellen Sie eine Sicherung wieder her oder erstellen Sie die Bibliothek neu, bevor Sie XML-Informationen erneut einlesen. + + + + Unable to load the archive backend: %1 + Das Archiv-Backend konnte nicht geladen werden: %1 + + + + Unable to start an XML rescan transaction. + Eine Transaktion zum erneuten Einlesen der XML-Informationen konnte nicht gestartet werden. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Der ausgewählte Ordner ist nicht mehr in dieser Bibliothek. Laden Sie die Bibliothek vor dem erneuten Einlesen neu. + + + + Unable to read comics for the XML rescan. + Comics für das erneute Einlesen der XML-Informationen konnten nicht gelesen werden. + + + + Unable to commit XML metadata changes. + Änderungen an XML-Metadaten konnten nicht bestätigt werden. + + + + Unable to update XML metadata for %1. + XML-Metadaten für %1 konnten nicht aktualisiert werden. + + YACReaderFieldEdit Restore to default - Standardeinstellungen wiederherstellen + Standardwert wiederherstellen - + Click to overwrite Zum Überschreiben anklicken @@ -4818,13 +6161,15 @@ Use quotes to include spaces in a value. Restore to default - Standardeinstellungen wiederherstellen + Standardwert wiederherstellen + + - - - + + + Click to overwrite Zum Überschreiben anklicken diff --git a/YACReaderLibrary/yacreaderlibrary_en.ts b/YACReaderLibrary/yacreaderlibrary_en.ts index 0aceb3ef1..d602a996d 100644 --- a/YACReaderLibrary/yacreaderlibrary_en.ts +++ b/YACReaderLibrary/yacreaderlibrary_en.ts @@ -11,6 +11,66 @@ AddLabelDialog + + + red + red + + + + orange + orange + + + + yellow + yellow + + + + green + green + + + + cyan + cyan + + + + blue + blue + + + + violet + violet + + + + purple + purple + + + + pink + pink + + + + white + white + + + + light + light + + + + dark + dark + Label name: @@ -51,13 +111,13 @@ - Comics folder : - Comics folder : + Comics folder: + Comics folder: - Library name : - Library name : + Library name: + Library name: @@ -69,8 +129,8 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> @@ -200,103 +260,625 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Hide comic flow + + Find comic › + Find comic › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Copying comics... + + Series + Series - Moving comics... - Moving comics... + + Number + Number + + + + Volume + Volume + + + + Year + Year + + + + Format + Format + + + + + ComicVine ID + ComicVine ID + + + + series and number + series and number + + + + series without the volume, and number + series without the volume, and number + + + + letters and digits of the series, and number + letters and digits of the series, and number + + + + series with its year, and number + series with its year, and number + + + + volume %1 + volume %1 + + + + Comic in your library + Comic in your library + + + + Missing in your library + Missing in your library + + + + Changed by you. + Changed by you. + + + + The entry has this comic. + The entry has this comic. + + + + Matched by %1. + Matched by %1. + + + + Your library already has this comic as missing. Matched by %1. + Your library already has this comic as missing. Matched by %1. + + + + Possible comic %1 + Possible comic %1 + + + + + Changed by you: it is imported as missing. + Changed by you: it is imported as missing. + + + + The comics have the same ComicVine ID. + The comics have the same ComicVine ID. + + + + The comic has the same series and number, but its volume or year is different. + The comic has the same series and number, but its volume or year is different. + + + + The comics have the same series and number. + The comics have the same series and number. + + + + Values that are different from the file are in amber. + Values that are different from the file are in amber. + + + + Closest series in your library + Closest series in your library + + + + Library + Library + + + + Has %1 + Has %1 + + + + No comic found. Tried: %1. + No comic found. Tried: %1. + + + + In the file + In the file + + + + Entry %1 of %2 + Entry %1 of %2 + + + + Use this comic + Use this comic + + + + Use this missing comic + Use this missing comic + + + + CblFindComicPage + + + ‹ All entries + ‹ All entries + + + + Previous problem + Previous problem + + + + Next problem + Next problem + + + + Folders + Folders + + + + Search the whole library + Search the whole library + + + + File + File + + + + Series + Series + + + + No. + No. + + + + Volume + Volume + + + + Year + Year + + + + Go to the next problem after I choose + Go to the next problem after I choose + + + + Use this comic + Use this comic + + + + Cancel + Cancel + + + + Search in this folder + Search in this folder + + + + Entry %1 of %2 + Entry %1 of %2 + + + + Missing: no comic found. Browse or search the library. + Missing: no comic found. Browse or search the library. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Ambiguous: %n possible comic. Browse or search the library. + Ambiguous: %n possible comics. Browse or search the library. + + + + + Browse or search the library to choose another comic. + Browse or search the library to choose another comic. + + + + Problem %1 of %2 + Problem %1 of %2 + + + + %n problem(s) + + %n problem + %n problems + + + + + + + Library + Library + + + + Search results + Search results + + + + CblImportDialog + + + Match to library comics — %1 + Match to library comics — %1 + + + + Apply + Apply + + + + Close + Close + + + + Skip + Skip + + + + Import + Import + + + + &Name: + &Name: + + + + A reading list with this name already exists. Import creates another one. + A reading list with this name already exists. Import creates another one. + + + + Filter entries + Filter entries + + + + Matching the books of %1 with your library... + Matching the books of %1 with your library... + + + + Matching the entries of %1 with your library... + Matching the entries of %1 with your library... + + + + + Match to library comics + Match to library comics + + + + This also gives comics to entries in %n other list(s): %1 + + This also gives comics to entries in %n other list: %1 + This also gives comics to entries in %n other lists: %1 + + + + + The changes could not be applied. Nothing was changed. + The changes could not be applied. Nothing was changed. + + + + The file cannot be opened. + The file cannot be opened. + + + + The file is damaged (line %1, column %2). + The file is damaged (line %1, column %2). + + + + The file is not a reading list. + The file is not a reading list. + + + + The library cannot be read. + The library cannot be read. + + + + Import reading lists + Import reading lists + + + + The reading list could not be imported. Nothing was written. + The reading list could not be imported. Nothing was written. + + + + Import reading lists — %1 (%2 of %3) + Import reading lists — %1 (%2 of %3) + + + + Import reading lists — %1 + Import reading lists — %1 + + + + All (%1) + All (%1) + + + + Matched (%1) + Matched (%1) + + + + Ambiguous (%1) + Ambiguous (%1) + + + + Missing (%1) + Missing (%1) + + + + Changed by you (%1) + Changed by you (%1) + + + + %1 of %n have a comic + + %1 of %n has a comic + %1 of %n have a comic + + + + + Find comic... + Find comic... + + + + Use the original match + Use the original match + + + + Make missing + Make missing + + + + Import as missing + Import as missing + + + + CblImportModel + + + Matched + Matched + + + + Ambiguous + Ambiguous + + + + Missing + Missing + + + + Changed + Changed + + + + # + # + + + + In the file + In the file + + + + Comic in your library + Comic in your library + + + + Status + Status + + + + Already missing in your library + Already missing in your library + + + + %n possible comic(s) + + %n possible comic + %n possible comics + + + + + Not in library + Not in library + + + + CblMatchComicDialog + + + Match to library comic + Match to library comic + + + + Loading the library... + Loading the library... + + + + The library cannot be read. + The library cannot be read. + + + + ClassicComicsView + + + Hide comic flow + Hide comic flow ComicInfoView - + Characters Characters - + Main character or team Main character or team - + Teams Teams - + Locations Locations - + Authors Authors - + writer writer - + penciller penciller - + inker inker - + colorist colorist - + letterer letterer - + cover artist cover artist - + editor editor - + imprint imprint - + Publisher Publisher - + color color - + b/w b/w @@ -304,28 +886,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader not found - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader not found. There might be a problem with your YACReader installation. - + Error Error - + Error opening comic with third party reader. Error opening comic with third party reader. @@ -333,67 +915,67 @@ ComicModel - + yes yes - + no no - + Title Title - + File Name File Name - + Pages Pages - + Size Size - + Read Read - + Current Page Current Page - + Publication Date Publication Date - + Rating Rating - + Series Series - + Volume Volume - + Story Arc Story Arc @@ -401,66 +983,110 @@ ComicVineDialog - + skip skip - + back back - + next next - + search search - + close close - - - + + + Looking for volume... Looking for volume... - - + + comic %1 of %2 - %3 comic %1 of %2 - %3 - + %1 comics selected %1 comics selected - - Error connecting to ComicVine - Error connecting to ComicVine + + Error connecting to Comic Vine + Error connecting to Comic Vine - - - Retrieving tags for : %1 - Retrieving tags for : %1 + + + Retrieving tags for: %1 + Retrieving tags for: %1 - + + Creating missing comic %1 of %2 + Creating missing comic %1 of %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine did not answer for %n comic. + Comic Vine did not answer for %n comics. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n missing comic got only the basic data of the volume. + %n missing comics got only the basic data of the volume. + + + + + %n missing comic(s) have no cover. + + %n missing comic has no cover. + %n missing comics have no cover. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + + + + Try again later for the comics that were not updated. + Try again later for the comics that were not updated. + + + + Comic Vine + Comic Vine + + + Retrieving volume info... Retrieving volume info... - + Looking for comic... Looking for comic... @@ -492,18 +1118,18 @@ - Comics folder : - Comics folder : + Comics folder: + Comics folder: - Library Name : - Library Name : + Library Name: + Library Name: - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. @@ -519,11 +1145,19 @@ DBHelper - + The folder entry could not be found in the library database. The folder entry could not be found in the library database. + + DelayedProgressDialog + + + Cancel + Cancel + + EditShortcutsDialog @@ -533,8 +1167,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. @@ -548,8 +1182,8 @@ - The shortcut "%1" is already assigned to other function - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function + The shortcut "%1" is already assigned to another function @@ -581,6 +1215,14 @@ This label doesn't contain comics yet + + EmptyListFolderWidget + + + This folder is empty + This folder is empty + + EmptyReadingListWidget @@ -621,8 +1263,8 @@ - Output file : - Output file : + Output file: + Output file: @@ -659,8 +1301,8 @@ - Output folder : - Output folder : + Output folder: + Output folder: @@ -686,34 +1328,27 @@ FileComic - + 7z not found 7z not found - + CRC error on page (%1): some of the pages will not be displayed correctly CRC error on page (%1): some of the pages will not be displayed correctly - + Unknown error opening the file Unknown error opening the file - - + + Format not supported Format not supported - - FolderContentView - - Continue Reading... - Continue Reading... - - FolderInfoView @@ -788,53 +1423,37 @@ GridComicsView - - Show info - Show info - - - Library - Library - - - Folder - Folder - - - Favorites - Favorites - - - Recently added - Recently added + + Show info + Show info - + Manga Manga - + Western manga Western manga - + Web comic Web comic - + Yonkoma Yonkoma - + Comic Comic - + Unknown Unknown @@ -876,8 +1495,8 @@ - Info database location : - Info database location : + Info database location: + Info database location: @@ -894,8 +1513,8 @@ ImportLibraryDialog - Destination folder : - Destination folder : + Destination folder: + Destination folder: @@ -909,18 +1528,18 @@ - Compresed library covers (*.clc) - Compresed library covers (*.clc) + Compressed library covers (*.clc) + Compressed library covers (*.clc) - Package location : - Package location : + Package location: + Package location: - Library Name : - Library Name : + Library Name: + Library Name: @@ -947,8 +1566,8 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> @@ -957,8 +1576,8 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> @@ -977,8 +1596,8 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> @@ -986,6 +1605,36 @@ <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Unable to create the library data folder: %1. Check folder access and available disk space. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + The library data folder is missing: %1. Restore it or recreate the library before updating. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + The library database is missing: %1. Restore a backup or recreate the library before updating. + + + + + Unable to start a library database transaction: %1 + Unable to start a library database transaction: %1 + + + + + Unable to commit the library database: %1 + Unable to commit the library database: %1 + + LibraryInfoView @@ -1009,108 +1658,201 @@ Number of read comics + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + + + + Retry + Retry + + + + Recover and retry + Recover and retry + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + The library lock owner changed. Retry the operation and review the new owner before recovering it. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Unable to create the library lock file: %1. Check write permissions for the library data folder. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + The library has a maintenance lock whose owner could not be verified (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Another maintenance operation is using this library (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + + LibraryManagementCoordinator - + Error opening the library Error opening the library - + Error creating the library Error creating the library - + Error updating the library Error updating the library + + + + XML information rescan + XML information rescan + + + + Restore recovery + Restore recovery + + + + Library upgrade + Library upgrade + + + + Library maintenance + Library maintenance + + + + Recover library access + Recover library access + LibraryWindow - - Do you want remove - Do you want remove + + Do you want to remove the library '%1'? + Do you want to remove the library '%1'? - + YACReader Library YACReader Library - + Are you sure? Are you sure? + Delete folder Delete folder - + Upgrade failed Upgrade failed - + There were errors during library upgrade in: There were errors during library upgrade in: - + Restore recovery failed Restore recovery failed - + Update needed Update needed - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - + Download new version Download new version - + This library was created with a newer version of YACReaderLibrary. Download the new version now? This library was created with a newer version of YACReaderLibrary. Download the new version now? - + Library not available Library not available - + Library '%1' is no longer available. Do you want to remove it? Library '%1' is no longer available. Do you want to remove it? - + Old library Old library - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - + Copying comics... Copying comics... - + Moving comics... Moving comics... @@ -1130,8 +1872,8 @@ - Please, select a folder first - Please, select a folder first + Please select a folder first + Please select a folder first @@ -1149,84 +1891,113 @@ The selected folder and all its contents will be deleted from your disk. Are you sure? - + Unable to delete Unable to delete - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. + + + + Edit smart list... + Edit smart list... + + + + Delete smart list + Delete smart list + + + + + + New folder... + New folder... + + + + + + Move to folder... + Move to folder... + + + + Rename folder... + Rename folder... + + + + Match to library comics... + Match to library comics... + + + + Track missing issues... + Track missing issues... + + + + Export reading list... + Export reading list... - + Rename or organize files Rename or organize files - + Set the type of the selected comics Set the type of the selected comics - + + Save as smart list… + Save as smart list… + + + Search filters Search filters - + Unread Unread - + In progress In progress - + Highly rated Highly rated - + Recently added Recently added - + Search syntax… Search syntax… - - A repair of this library is already running (%1). Wait for it to finish. - A repair of this library is already running (%1). Wait for it to finish. - - - - The library is locked by a repair that did not finish. - The library is locked by a repair that did not finish. - - - - The library is locked by a repair started by %1. - The library is locked by a repair started by %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - - - + Package operation failed Package operation failed - + The covers package operation could not be completed. The covers package operation could not be completed. @@ -1277,81 +2048,75 @@ Folder: %1 The library database could not be updated, and the folder rename on disk could not be reverted. The library now needs to be updated manually. - + Save covers Save covers - + You are adding too many libraries. You are adding too many libraries. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. - + Library not found Library not found - + The selected folder doesn't contain any library. The selected folder doesn't contain any library. - - + + YACReader library database (*.ydb) YACReader library database (*.ydb) - + The library database backup was created at: %1 The library database backup was created at: %1 - + Unable to create the library database backup: %1 Unable to create the library database backup: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? - + Restoring library database... Restoring library database... - + The current library database is invalid. Restore the selected backup anyway? The current library database is invalid. Restore the selected backup anyway? - - - The library maintenance lock may be stale. Remove it and retry? - The library maintenance lock may be stale. Remove it and retry? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1360,71 +2125,77 @@ Restart YACReaderLibrary before attempting recovery again. Restart YACReaderLibrary before attempting recovery again. - + The library database was restored successfully. Update the library now? The library database was restored successfully. Update the library now? - + Library database damaged Library database damaged - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. - + Attempt repair Attempt repair - + Restore a backup... Restore a backup... - + Repairing library database... Repairing library database... - - + + + Library database repair Library database repair - - Another maintenance operation is currently using this library. Try again after it finishes. - Another maintenance operation is currently using this library. Try again after it finishes. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + + + + Check and repair + Check and repair - + The library database is already valid. The library database is already valid. - + Library database repaired Library database repaired - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 - + Library database rebuilt Library database rebuilt - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1435,7 +2206,7 @@ Update the library now? Update the library now? - + The damaged original was preserved at: @@ -1446,12 +2217,12 @@ The damaged original was preserved at: %1 - + Library database repair failed Library database repair failed - + The library database could not be repaired: %1%2 @@ -1462,91 +2233,127 @@ You can restore a backup from the Library menu or recreate the library. You can restore a backup from the Library menu or recreate the library. - - library? - library? - - - + Remove and delete metadata and backups Remove and delete metadata and backups - + Library info Library info - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. - + Assign comics numbers Assign comics numbers - - Assign numbers starting in: - Assign numbers starting in: + + Assign numbers starting at: + Assign numbers starting at: - + Invalid image Invalid image - + The selected file is not a valid image. The selected file is not a valid image. - + + Error saving cover Error saving cover - + + There was an error saving the cover image. There was an error saving the cover image. - + + + Match to library comic + Match to library comic + + + + This also gives the comic to entries in %n other list(s): %1 + + This also gives the comic to entries in %n other list: %1 + This also gives the comic to entries in %n other lists: %1 + + + + + The comic could not be matched. Nothing was changed. + The comic could not be matched. Nothing was changed. + + + + The selected missing comics will be removed from this folder. Are you sure? + The selected missing comics will be removed from this folder. Are you sure? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + + + Delete comics Delete comics - + All the selected comics will be deleted from your disk. Are you sure? All the selected comics will be deleted from your disk. Are you sure? - + + Remove the comics from Reading? Their reading progress will be reset. + Remove the comics from Reading? Their reading progress will be reset. + + + + The comics will be removed from Favorites. Are you sure? + The comics will be removed from Favorites. Are you sure? + + + Remove comics Remove comics - + Comics will only be deleted from the current label/list. Are you sure? Comics will only be deleted from the current label/list. Are you sure? - + Library name already exists Library name already exists - + There is another library with the name '%1'. There is another library with the name '%1'. - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1631,433 +2438,504 @@ Missing files: %3 + Repair library database... + Repair library database... + + + + Check the current library database and attempt to repair any damage + Check the current library database and attempt to repair any damage + + + + Recover library access... + Recover library access... + + + + Recover access after an interrupted library maintenance operation + Recover access after an interrupted library maintenance operation + + + Repair covers and comic info Repair covers and comic info - + Retry comics with missing covers or incomplete information Retry comics with missing covers or incomplete information - + Rename library Rename library - + Rename current library Rename current library - + Remove library Remove library - + Remove current library from your collection Remove current library from your collection - + Rescan library for XML info Rescan library for XML info - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. - + Open library folder... Open library folder... - + Open the root folder of the current library Open the root folder of the current library - + Show library info Show library info - + Show information about the current library Show information about the current library - + Open current comic Open current comic - - Open current comic on YACReader - Open current comic on YACReader + + Open current comic in YACReader + Open current comic in YACReader - + Save selected covers to... Save selected covers to... - + Save covers of the selected comics as JPG files Save covers of the selected comics as JPG files - - + + Set as read Set as read - + Set comic as read Set comic as read - - + + Set as unread Set as unread - + Set comic as unread Set comic as unread - - + + manga manga - + Set issue as manga Set issue as manga - - + + comic comic - + Set issue as normal Set issue as normal - + western manga western manga - + Set issue as western manga Set issue as western manga - - + + web comic web comic - + Set issue as web comic Set issue as web comic - - + + yonkoma yonkoma - + Set issue as yonkoma Set issue as yonkoma - + Show/Hide marks Show/Hide marks - + Show or hide read marks Show or hide read marks - + Show/Hide recent indicator Show/Hide recent indicator - + Show or hide recent indicator Show or hide recent indicator - - + + Fullscreen mode on/off Fullscreen mode on/off - + Help, About YACReader Help, About YACReader - + Add new folder Add new folder - + Add new folder to the current library Add new folder to the current library - + Rename folder Rename folder - + Rename the current folder on disk and in the library Rename the current folder on disk and in the library - + Delete folder Delete folder - + Delete current folder from disk Delete current folder from disk - + Select root node Select root node - + Expand all nodes Expand all nodes - + Collapse all nodes Collapse all nodes - + Show options dialog Show options dialog - + Show comics server options dialog Show comics server options dialog - - + + Change between comics views Change between comics views - + + Grid view + Grid view + + + + Flow view + + + + + Info view + + + + Open folder... Open folder... - - + + Rename files... Organize files Rename files... - - + + Organize into folders... Organize into folders... - + Set as uncompleted Set as uncompleted - + Set as completed Set as completed - + Set custom cover Set custom cover - + Delete custom cover Delete custom cover - + western manga (left to right) western manga (left to right) - + Open containing folder... Open containing folder... - Reset comic rating - Reset comic rating + + New reading list + New reading list - + Select all comics Select all comics - + Edit Edit - + Assign current order to comics Assign current order to comics - + Update cover Update cover - + Delete selected comics Delete selected comics - + Delete metadata from selected comics Delete metadata from selected comics - + Download tags from Comic Vine Download tags from Comic Vine - + Focus search line Focus search line - + Focus comics view Focus comics view - + Edit shortcuts Edit shortcuts - + &Quit &Quit - + Update folder Update folder - + Update current folder Update current folder - + Scan legacy XML metadata Scan legacy XML metadata - - Add new reading list - Add new reading list - - - + Add a new reading list to the current library Add a new reading list to the current library - + + New smart list... + New smart list... + + + + Add a saved search to the current library + Add a saved search to the current library + + + + New folder... + New folder... + + + + Add a folder for reading lists or smart lists + Add a folder for reading lists or smart lists + + + + Import reading lists... + Import reading lists... + + + + Import CBL reading lists into the current library + Import CBL reading lists into the current library + + + + New + New + + + + Add or import reading lists + Add or import reading lists + + + Remove reading list Remove reading list - + Remove current reading list from the library Remove current reading list from the library - + Add new label Add new label - + Add a new label to this library Add a new label to this library - + Rename selected list Rename selected list - + Rename any selected labels or lists Rename any selected labels or lists - + Add to... Add to... - + Favorites Favorites - + Add selected comics to favorites list Add selected comics to favorites list - - + + Reset rating Reset rating @@ -2086,91 +2964,104 @@ Missing files: %3 - 4koma (top to botom) - 4koma (top to botom) + 4koma (top to bottom) + 4koma (top to bottom) - - - + + + Set type Set type - + Library Library - + Folder Folder - + Comic Comic - + + Match to library comic... + Match to library comic... + + + + + + + Track missing issues... + Track missing issues... + + + Open folder... Open folder... - + Update folder Update folder - + Rename folder Rename folder - + Rename files... Rename files... - + Organize into folders... Organize into folders... - + Rescan library for XML info Rescan library for XML info - + Set as uncompleted Set as uncompleted - + Set as completed Set as completed - + Set as read Set as read - - + + Set as unread Set as unread - + Set custom cover Set custom cover - - + + Delete custom cover Delete custom cover @@ -2203,17 +3094,63 @@ Missing files: %3 1 sublist - - %1 sublists - %1 sublists + + %1 sublists + %1 sublists + + + + LocalComicListModel + + + file name + file name + + + + MoveToFolderDialog + + + Move to folder + Move to folder + + + + Top level + Top level + + + + Move "%1" to: + Move "%1" to: + + + + NewListFolderDialog + + + New folder + New folder + + + + Reading lists + Reading lists + + + + Smart lists + Smart lists + + + + Folder name: + Folder name: - - - LocalComicListModel - - file name - file name + + For: + For: @@ -2225,8 +3162,8 @@ Missing files: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> @@ -2276,8 +3213,8 @@ Missing files: %3 - Start into the system tray - Start into the system tray + Start in the system tray + Start in the system tray @@ -2387,17 +3324,17 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. @@ -2431,8 +3368,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Restore defautls - Restore defautls + Restore defaults + Restore defaults @@ -2509,12 +3446,93 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFiles - + + + Unknown Series + Unknown Series + + + + series + series + + + + Unknown Publisher + Unknown Publisher + + + + publisher + publisher + + + + title + title + + + + Unknown Comic + Unknown Comic + + + + Series #Number - Title + Series #Number - Title + + + + Series #Number + Series #Number + + + + Series #Number (of Count) + Series #Number (of Count) + + + + Number - Title + Number - Title + + + + Series #Number (Year) + Series #Number (Year) + + + + Publisher / Series / Series #Number - Title + Publisher / Series / Series #Number - Title + + + + Publisher / Imprint / Series / Series #Number - Title + Publisher / Imprint / Series / Series #Number - Title + + + + Series / Series #Number + Series / Series #Number + + + + Publisher / Series / Series #Number (Year) + Publisher / Series / Series #Number (Year) + + + + Series / original file name + Series / original file name + + + Renamed, %1 is already in use Renamed, %1 is already in use - + Missing metadata: %1 Missing metadata: %1 @@ -2523,62 +3541,82 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %1 could not be created %1 could not be created + + + %1 (copy also failed: %2) + %1 (copy also failed: %2) + + + + the copy did not have the same size as the original + the copy did not have the same size as the original + + + + the original could not be deleted after it was copied + the original could not be deleted after it was copied + + + + The destination folder could not be created. + The destination folder could not be created. + + + + %1 (the file was left at %2) + %1 (the file was left at %2) + OrganizeFilesCoordinator - - + + Organize files Organize files - + This folder does not contain any comics. This folder does not contain any comics. - - This library is busy: %1 - This library is busy: %1 - - - + the library database could not be opened the library database could not be opened - + the library database could not be locked for writing the library database could not be locked for writing - + a folder entry could not be restored a folder entry could not be restored - + a comic entry could not be updated a comic entry could not be updated - + the library database could not be saved: %1 the library database could not be saved: %1 - + the record of the last organize run could not be read the record of the last organize run could not be read - + the folder %1 could not be created the folder %1 could not be created - + %n file(s) could not be moved back %n file could not be moved back @@ -2588,10 +3626,6 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFilesDialog - - Format: - Format: - Organize files @@ -2723,19 +3757,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Cancel Cancel - - Copy the list - Copy the list - Undo Undo - - Close - Close - Copy failure details @@ -3154,274 +4180,386 @@ To stop an automatic update tap on the loading indicator next to the Libraries t PropertiesDialog - + General info General info - + Authors Authors - + Publishing Publishing - + Plot Plot - + Notes Notes - + Cover page Cover page - + Load previous page as cover Load previous page as cover - + Load next page as cover Load next page as cover - + Reset cover to the default image Reset cover to the default image - + Load custom cover image Load custom cover image - + Series: Series: - + Title: Title: - - + + e.g. 1-5, 8, 0.5, Annual 1 + e.g. 1-5, 8, 0.5, Annual 1 + + + + of: of: - + + Issue number: Issue number: - + Volume: Volume: - + Arc number: Arc number: - + Story arc: Story arc: - + alt. number: alt. number: - + Alternate series: Alternate series: - + Series Group: Series Group: - + Genre: Genre: - + Size: Size: - + Writer(s): Writer(s): - + Penciller(s): Penciller(s): - + Inker(s): Inker(s): - + Colorist(s): Colorist(s): - + Letterer(s): Letterer(s): - + Cover Artist(s): Cover Artist(s): - + Editor(s): Editor(s): - + Imprint: Imprint: - + Day: Day: - + Month: Month: - + Year: Year: - + Publisher: Publisher: - + Format: Format: - + Color/BW: Color/BW: - + Age rating: Age rating: - + Type: Type: - + Language (ISO): Language (ISO): - + Synopsis: Synopsis: - + Characters: Characters: - + Teams: Teams: - + Locations: Locations: - + Main character or team: Main character or team: - + Review: Review: - + Notes: Notes: - + Tags: Tags: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - + Not found Not found - + Comic not found. You should update your library. Comic not found. You should update your library. - + Edit selected comics information Edit selected comics information - + + + Track missing issues in "%1" + Track missing issues in "%1" + + + + Issues: + Issues: + + + + Track + Track + + + + At most %1 issues can be tracked at once. + At most %1 issues can be tracked at once. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Check the issues: a range must go up, and a part between commas cannot be empty. + + + + No new issues to track. + No new issues to track. + + + + %n issue(s) will be tracked: %1. + + %n issue will be tracked: %1. + %n issues will be tracked: %1. + + + + + That is a lot of issues; check the numbers. + That is a lot of issues; check the numbers. + + + + %n issue(s) already in the folder. + + %n issue already in the folder. + %n issues already in the folder. + + + + + %n repeated issue(s). + + %n repeated issue. + %n repeated issues. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Issues not tracked + + + + The missing issues could not be tracked. + The missing issues could not be tracked. + + + + Comic skipped + Comic skipped + + + + A comic was removed from the library or now has a different file, so it is skipped. + A comic was removed from the library or now has a different file, so it is skipped. + + + + + Changes not saved + Changes not saved + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + + + + The changes could not be saved. + The changes could not be saved. + + + Invalid cover Invalid cover - + The image is invalid. The image is invalid. - + Edit comic information Edit comic information @@ -3439,12 +4577,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t unable to load 7z lib from ./utils - + Select custom cover Select custom cover - + Images (%1) Images (%1) @@ -3477,31 +4615,166 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists Add new reading lists - - + + List name: List name: - + + Import reading lists + Import reading lists + + + + Reading lists (*.cbl *.json);;All files (*) + Reading lists (*.cbl *.json);;All files (*) + + + + New smart list + New smart list + + + + + The smart list could not be saved. + The smart list could not be saved. + + + + Edit smart list + Edit smart list + + + + Delete folder + Delete folder + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + + + Delete list/label Delete list/label - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - + Rename list name Rename list name + + + Rename folder + Rename folder + + + + + Folder name: + Folder name: + + + + + + New folder + New folder + + + + + The folder could not be created. + The folder could not be created. + + + + Reading list + Reading list + + + + CBL reading list (*.cbl) + CBL reading list (*.cbl) + + + + JSON reading list (*.json) + JSON reading list (*.json) + + + + Web page (*.html) + Web page (*.html) + + + + + Export reading list + Export reading list + + + + Exporting reading list... + Exporting reading list... + + + + The file could not be written. + The file could not be written. + + + + Move to folder + Move to folder + + + + "%1" could not be moved. + "%1" could not be moved. + + + + ReadingListPage + + + %n comic(s) + + %n comic + %n comics + + + + + %n missing + + %n missing + %n missing + + + + + Missing + Missing + + + + Exported from YACReader on %1 + Exported from YACReader on %1 + RenameLibraryDialog @@ -3522,16 +4795,16 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - New Library Name : - New Library Name : + New Library Name: + New Library Name: ScraperResultsPaginator - Number of volumes found : %1 - Number of volumes found : %1 + Number of volumes found: %1 + Number of volumes found: %1 @@ -3541,8 +4814,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of %1 found : %2 - Number of %1 found : %2 + Number of %1 found: %2 + Number of %1 found: %2 @@ -3904,6 +5177,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. Just start typing. Plain text search across all metadata. + + + %1 -> search for `%1` in any database field + %1 -> search for `%1` in any database field + 1. Search everywhere @@ -3999,7 +5277,9 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. + Tips: +Spaces act like AND, and searches are not case-sensitive. +Use quotes to include spaces in a value. @@ -4154,8 +5434,8 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. - Please, select the right comic info. + Please select the right comic info. + Please select the right comic info. @@ -4182,8 +5462,8 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. - Please, select the right series for your comic. + Please select the right series for your comic. + Please select the right series for your comic. @@ -4221,62 +5501,67 @@ Use quotes to include spaces in a value. Unknown - Unknown + Unknown %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 unknown) %1 series - + %1 series - + %1 comics selected %n comic(s) selected - %1 comics selected + %1 comics selected - + Read - Read + Read - + In progress - In progress + In progress - + Unread - Unread + Unread - + Total pages - + Total pages - + Total size - + Total size + + + + Missing + Missing - + Series - Series + Series SeriesQuestion - You are trying to get information for various comics at once, are they part of the same series? - You are trying to get information for various comics at once, are they part of the same series? + You are trying to get information for various comics at once. Are they part of the same series? + You are trying to get information for various comics at once. Are they part of the same series? @@ -4297,22 +5582,6 @@ Use quotes to include spaces in a value. set port Set port - - Server connectivity information - Server connectivity information - - - Scan it! - Scan it! - - - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - - - Choose an IP address - Choose an IP address - @@ -4364,38 +5633,70 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. + + + SmartListDialog + + + New smart list + New smart list + + + + Edit smart list + Edit smart list + + + + Search filters and syntax + Search filters and syntax + + + + Name: + Name: + - enable the server - enable the server + + Search: + Search: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. + Please sort the list of comics on the left until it matches the comics' information. - + sort comics to match comic information sort comics to match comic information - + issues issues - + remove selected comics remove selected comics - + restore all removed comics restore all removed comics + + + Add the %n other issue(s) of this volume as missing comics + + Add the %n other issue of this volume as a missing comic + Add the %n other issues of this volume as missing comics + + ThemeEditorDialog @@ -4611,8 +5912,8 @@ Use quotes to include spaces in a value. - Updating.... - Updating.... + Updating... + Updating... @@ -4620,6 +5921,14 @@ Use quotes to include spaces in a value. Update library + + ViewSelector + + + View + + + VolumeComicsModel @@ -4755,8 +6064,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Use VSync (improve the image quality in fullscreen mode, worse performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) @@ -4795,11 +6104,49 @@ Use quotes to include spaces in a value. Previous versions + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + + + + Unable to load the archive backend: %1 + Unable to load the archive backend: %1 + + + + Unable to start an XML rescan transaction. + Unable to start an XML rescan transaction. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + The selected folder is no longer in this library. Reload the library before rescanning. + + + + Unable to read comics for the XML rescan. + Unable to read comics for the XML rescan. + + + + Unable to commit XML metadata changes. + Unable to commit XML metadata changes. + + + + Unable to update XML metadata for %1. + Unable to update XML metadata for %1. + + YACReaderFieldEdit - + Click to overwrite Click to overwrite @@ -4812,10 +6159,12 @@ Use quotes to include spaces in a value. YACReaderFieldPlainTextEdit + + - - - + + + Click to overwrite Click to overwrite diff --git a/YACReaderLibrary/yacreaderlibrary_es.ts b/YACReaderLibrary/yacreaderlibrary_es.ts index 8e2f623dd..816df4fce 100644 --- a/YACReaderLibrary/yacreaderlibrary_es.ts +++ b/YACReaderLibrary/yacreaderlibrary_es.ts @@ -21,6 +21,66 @@ Label name: Nombre de la etiqueta: + + + red + Rojo + + + + orange + Naranja + + + + yellow + Amarillo + + + + green + Verde + + + + cyan + Cian + + + + blue + Azul + + + + violet + Violeta + + + + purple + Púrpura + + + + pink + Rosa + + + + white + Blanco + + + + light + Claro + + + + dark + Oscuro + Choose a color: @@ -51,13 +111,13 @@ - Comics folder : - Carpeta de cómics : + Comics folder: + Carpeta de cómics: - Library name : - Nombre de la biblioteca : + Library name: + Nombre de la biblioteca: @@ -69,7 +129,7 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> Antes de que te puedas conectar a Comic Vine necesitas tu propia clave API. Por favor, obtén una gratis <a href="http://www.comicvine.com/api/">aquí</a> @@ -98,7 +158,7 @@ Light - Luz + Claro @@ -200,103 +260,625 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Ocultar Comic Flow + + Find comic › + Buscar cómic › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Copiando cómics... + + Series + Serie - Moving comics... - Moviendo cómics... + + Number + Número + + + + Volume + Volumen + + + + Year + Año + + + + Format + Formato + + + + + ComicVine ID + ID de Comic Vine + + + + series and number + serie y número + + + + series without the volume, and number + serie sin volumen y número + + + + letters and digits of the series, and number + letras y dígitos de la serie y número + + + + series with its year, and number + serie con año y número + + + + volume %1 + volumen %1 + + + + Comic in your library + Cómic en tu biblioteca + + + + Missing in your library + Falta en tu biblioteca + + + + Changed by you. + Modificado por ti. + + + + The entry has this comic. + La entrada tiene este cómic. + + + + Matched by %1. + Coincidencia por %1. + + + + Your library already has this comic as missing. Matched by %1. + Tu biblioteca ya tiene este cómic como ausente. Coincidencia por %1. + + + + Possible comic %1 + Posible cómic %1 + + + + + Changed by you: it is imported as missing. + Modificado por ti: se importa como ausente. + + + + The comics have the same ComicVine ID. + Los cómics tienen el mismo ID de Comic Vine. + + + + The comic has the same series and number, but its volume or year is different. + El cómic tiene la misma serie y número, pero el volumen o el año son distintos. + + + + The comics have the same series and number. + Los cómics tienen la misma serie y número. + + + + Values that are different from the file are in amber. + Los valores distintos a los del archivo aparecen en ámbar. + + + + Closest series in your library + Series más parecidas de tu biblioteca + + + + Library + Biblioteca + + + + Has %1 + Tiene %1 + + + + No comic found. Tried: %1. + No se encontró ningún cómic. Se intentó: %1. + + + + In the file + En el archivo + + + + Entry %1 of %2 + Entrada %1 de %2 + + + + Use this comic + Usar este cómic + + + + Use this missing comic + Usar este cómic ausente + + + + CblFindComicPage + + + ‹ All entries + ‹ Todas las entradas + + + + Previous problem + Problema anterior + + + + Next problem + Siguiente problema + + + + Folders + Carpetas + + + + Search the whole library + Buscar en toda la biblioteca + + + + File + Archivo + + + + Series + Serie + + + + No. + N.º + + + + Volume + Volumen + + + + Year + Año + + + + Go to the next problem after I choose + Ir al siguiente problema tras elegir + + + + Use this comic + Usar este cómic + + + + Cancel + Cancelar + + + + Search in this folder + Buscar en esta carpeta + + + + Entry %1 of %2 + Entrada %1 de %2 + + + + Missing: no comic found. Browse or search the library. + Ausente: no se encontró ningún cómic. Explora o busca en la biblioteca. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Ambiguo: %n posible cómic. Explora o busca en la biblioteca. + Ambiguo: %n posibles cómics. Explora o busca en la biblioteca. + + + + + Browse or search the library to choose another comic. + Explora o busca en la biblioteca para elegir otro cómic. + + + + Problem %1 of %2 + Problema %1 de %2 + + + + %n problem(s) + + %n problema + %n problemas + + + + + + + Library + Biblioteca + + + + Search results + Resultados de búsqueda + + + + CblImportDialog + + + Match to library comics — %1 + Emparejar con cómics de la biblioteca — %1 + + + + Apply + Aplicar + + + + Close + Cerrar + + + + Skip + Omitir + + + + Import + Importar + + + + &Name: + &Nombre: + + + + A reading list with this name already exists. Import creates another one. + Ya existe una lista de lectura con este nombre. La importación crea otra. + + + + Filter entries + Filtrar entradas + + + + Matching the books of %1 with your library... + Emparejando los libros de %1 con tu biblioteca... + + + + Matching the entries of %1 with your library... + Emparejando las entradas de %1 con tu biblioteca... + + + + + Match to library comics + Emparejar con cómics de la biblioteca + + + + This also gives comics to entries in %n other list(s): %1 + + También se asignan cómics a entradas de %n lista más: %1 + También se asignan cómics a entradas de %n listas más: %1 + + + + + The changes could not be applied. Nothing was changed. + No se pudieron aplicar los cambios. No se modificó nada. + + + + The file cannot be opened. + No se puede abrir el archivo. + + + + The file is damaged (line %1, column %2). + El archivo está dañado (línea %1, columna %2). + + + + The file is not a reading list. + El archivo no es una lista de lectura. + + + + The library cannot be read. + No se puede leer la biblioteca. + + + + Import reading lists + Importar listas de lectura + + + + The reading list could not be imported. Nothing was written. + No se pudo importar la lista de lectura. No se escribió nada. + + + + Import reading lists — %1 (%2 of %3) + Importar listas de lectura — %1 (%2 de %3) + + + + Import reading lists — %1 + Importar listas de lectura — %1 + + + + All (%1) + Todas (%1) + + + + Matched (%1) + Emparejadas (%1) + + + + Ambiguous (%1) + Ambiguas (%1) + + + + Missing (%1) + Ausentes (%1) + + + + Changed by you (%1) + Modificadas por ti (%1) + + + + %1 of %n have a comic + + %1 de %n tiene un cómic + %1 de %n tienen un cómic + + + + + Find comic... + Buscar cómic... + + + + Use the original match + Usar la coincidencia original + + + + Make missing + Marcar como ausente + + + + Import as missing + Importar como ausente + + + + CblImportModel + + + Matched + Emparejado + + + + Ambiguous + Ambiguo + + + + Missing + Ausente + + + + Changed + Modificado + + + + # + # + + + + In the file + En el archivo + + + + Comic in your library + Cómic en tu biblioteca + + + + Status + Estado + + + + Already missing in your library + Ya figura como ausente en tu biblioteca + + + + %n possible comic(s) + + %n posible cómic + %n posibles cómics + + + + + Not in library + No está en la biblioteca + + + + CblMatchComicDialog + + + Match to library comic + Emparejar con un cómic de la biblioteca + + + + Loading the library... + Cargando la biblioteca... + + + + The library cannot be read. + No se puede leer la biblioteca. + + + + ClassicComicsView + + + Hide comic flow + Ocultar Comic Flow ComicInfoView - + Main character or team Personaje principal o equipo - + Teams Equipos - + Locations Lugares - + Authors Autores - + writer - escritor + guionista - + penciller dibujante - + inker entintador - + colorist colorista - + letterer rotulista - + cover artist artista de portada - + editor editor - + imprint sello editorial - + Publisher Editorial - + color color - + b/w b/n - + Characters Personajes @@ -304,28 +886,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader no encontrado - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader no encontrado. YACReader debería estar instalado en la misma carpeta que YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader no encontrado. Podría haber un problema con tu instalación de YACReader. - + Error - Fallo + Error - + Error opening comic with third party reader. Error al abrir el cómic con una aplicación de terceros. @@ -333,134 +915,178 @@ ComicModel - + no - No + no - + yes sí - + Read Leído - + Series Serie - + Volume Volumen - + Story Arc Arco argumental - + Size Tamaño - + Pages Páginas - + Title Título - + Current Page - Página Actual + Página actual - + File Name Nombre de archivo - + Publication Date Fecha de publicación - + Rating - Nota + Valoración ComicVineDialog - + back atrás - + next siguiente - + skip omitir - + close cerrar - - - Retrieving tags for : %1 - Recuperando etiquetas para : %1 + + + Retrieving tags for: %1 + Recuperando etiquetas para: %1 + + + + Creating missing comic %1 of %2 + Creando el cómic ausente %1 de %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine no respondió para %n cómic. + Comic Vine no respondió para %n cómics. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n cómic ausente recibió solo los datos básicos del volumen. + %n cómics ausentes recibieron solo los datos básicos del volumen. + + + + + %n missing comic(s) have no cover. + + %n cómic ausente no tiene portada. + %n cómics ausentes no tienen portada. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine permite unas 200 solicitudes por hora; vuelve a intentarlo más tarde para los cómics que no se actualizaron. + + + + Try again later for the comics that were not updated. + Vuelve a intentarlo más tarde para los cómics que no se actualizaron. + + + + Comic Vine + Comic Vine - + Looking for comic... Buscando cómic... - + search buscar - - - + + + Looking for volume... Buscando volumen... - - + + comic %1 of %2 - %3 cómic %1 de %2 - %3 - + %1 comics selected %1 cómics seleccionados - - Error connecting to ComicVine - Error conectando a ComicVine + + Error connecting to Comic Vine + Error conectando a Comic Vine - + Retrieving volume info... Recuperando información del volumen... @@ -478,7 +1104,7 @@ Create new library - Crear la nueva biblioteca + Crear nueva biblioteca @@ -492,8 +1118,8 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - Crear una biblioteca puede llevar varios minutos. Puedes parar el proceso en cualquier momento y completar la tarea más tarde. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + Crear una biblioteca puede llevar varios minutos. Puedes parar el proceso y actualizar la biblioteca más tarde para completar la tarea. @@ -502,13 +1128,13 @@ - Comics folder : - Carpeta de cómics : + Comics folder: + Carpeta de cómics: - Library Name : - Nombre de la biblioteca : + Library Name: + Nombre de la biblioteca: @@ -519,11 +1145,19 @@ DBHelper - + The folder entry could not be found in the library database. No se ha encontrado la entrada de la carpeta en la base de datos de la biblioteca. + + DelayedProgressDialog + + + Cancel + Cancelar + + EditShortcutsDialog @@ -533,8 +1167,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - Para cambiar un atajo haz un doble click en la combinación de teclas y teclea nuevos valores. + To change a shortcut, double-click the key combination and type the new keys. + Para cambiar un atajo, haz doble clic en la combinación de teclas y escribe las nuevas teclas. @@ -548,7 +1182,7 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function El atajo "%1" ya está asignado a otra función @@ -581,12 +1215,20 @@ Esta etiqueta aún no contiene ningún cómic + + EmptyListFolderWidget + + + This folder is empty + Esta carpeta está vacía + + EmptyReadingListWidget This reading list does not contain any comics yet - Esta lista de tectura aún no contiene ningún cómic + Esta lista de lectura aún no contiene ningún cómic @@ -604,15 +1246,15 @@ There are no recent comics! - ¡No hay comics recientes! + ¡No hay cómics recientes! ExportComicsInfoDialog - Output file : - Archivo de salida : + Output file: + Archivo de salida: @@ -664,8 +1306,8 @@ - Output folder : - Carpeta de destino : + Output folder: + Carpeta de destino: @@ -686,34 +1328,27 @@ FileComic - - + + Format not supported Formato no soportado - + 7z not found 7z no encontrado - + Unknown error opening the file - Error desconocido abriendo el archivo + Error desconocido al abrir el archivo - + CRC error on page (%1): some of the pages will not be displayed correctly Error CRC en la página (%1): algunas de las páginas no se mostrarán correctamente - - FolderContentView - - Continue Reading... - Continúa leyendo... - - FolderInfoView @@ -788,53 +1423,37 @@ GridComicsView - + Show info Mostrar información - Library - Librería - - - Folder - Carpeta - - - Favorites - Favoritos - - - Recently added - Añadido recientemente - - - + Manga Manga - + Western manga Manga occidental - + Web comic Cómic web - + Yonkoma Yonkoma - + Comic Cómic - + Unknown Desconocido @@ -849,7 +1468,7 @@ System info - Información de systema + Información del sistema @@ -876,8 +1495,8 @@ - Info database location : - Ubicación de la base de datos de información : + Info database location: + Ubicación de la base de datos de información: @@ -894,8 +1513,8 @@ ImportLibraryDialog - Destination folder : - Directorio de destino : + Destination folder: + Carpeta de destino: @@ -909,18 +1528,18 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) Portadas de biblioteca comprimidas (*.clc) - Package location : - Ubicación del paquete : + Package location: + Ubicación del paquete: - Library Name : - Nombre de la biblioteca : + Library Name: + Nombre de la biblioteca: @@ -942,13 +1561,13 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> <p>YACReaderLibrary está creando una nueva biblioteca.</p><p>Crear una biblioteca puede llevar varios minutos. Puedes parar el proceso en cualquier momento y actualizar la biblioteca más tarde para completar el proceso.</p> Some of the comics being added... - Algunos de los cómics que estan siendo añadidos.... + Algunos de los cómics que se están añadiendo... @@ -957,8 +1576,8 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>La biblioteca actual está siendo actualizada. Para actualizaciones más rápidas, por favor, actualiza tus bibliotecas frecuentemente.</p><p>Puedes parar el proceso y continunar la actualización más tarde.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>La biblioteca actual está siendo actualizada. Para actualizaciones más rápidas, por favor, actualiza tus bibliotecas con frecuencia.</p><p>Puedes parar el proceso y continuar la actualización de esta biblioteca más tarde.</p> @@ -968,7 +1587,7 @@ <p>The current library is being upgraded, please wait.</p> - <p>La biblioteca actual está siendo actualizadad, espera por favor.</p> + <p>La biblioteca actual está siendo actualizada, espera por favor.</p> @@ -977,8 +1596,8 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>La biblioteca está siendo escaneada para encontrar metadatos en formato XML.</p><p>Sólo necesitas hacer esto una vez, y sólo si la biblioteca fue creada con YACReaderLibrary 9.8.2 o antes.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>La biblioteca actual está siendo escaneada en busca de metadatos XML heredados.</p><p>Solo necesitas hacer esto una vez, y solo si la biblioteca fue creada con YACReaderLibrary 9.8.2 o anterior.</p> @@ -986,6 +1605,36 @@ <p>Se está comprobando si faltan portadas o información de cómics incompleta en la biblioteca actual.</p><p>Esto puede tardar varios minutos. Puedes detener el proceso y volver a ejecutarlo más tarde.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + No se pudo crear la carpeta de datos de la biblioteca: %1. Comprueba el acceso a la carpeta y el espacio disponible. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Falta la carpeta de datos de la biblioteca: %1. Restáurala o vuelve a crear la biblioteca antes de actualizar. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Falta la base de datos de la biblioteca: %1. Restaura una copia de seguridad o vuelve a crear la biblioteca antes de actualizar. + + + + + Unable to start a library database transaction: %1 + No se pudo iniciar una transacción en la base de datos de la biblioteca: %1 + + + + + Unable to commit the library database: %1 + No se pudo confirmar la transacción en la base de datos de la biblioteca: %1 + + LibraryInfoView @@ -1009,153 +1658,237 @@ Número de cómics leídos + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Esta biblioteca está ocupada. Espera a que termine el mantenimiento. Si parece bloqueado, cierra la aplicación que aparece en Detalles y vuelve a intentarlo. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Es posible que una operación de mantenimiento anterior no haya liberado esta biblioteca. Antes de recuperar el acceso, asegúrate de que ninguna operación de YACReaderLibrary o YACReaderLibraryServer la esté usando, incluso en otro equipo. + + + + Retry + Reintentar + + + + Recover and retry + Recuperar y reintentar + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + El propietario del bloqueo de la biblioteca ha cambiado. Reintenta la operación y revisa el nuevo propietario antes de recuperar el acceso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + No se pudo eliminar el bloqueo de mantenimiento: %1. Asegúrate de que el archivo no sea de solo lectura y de que puedas escribir en la carpeta de datos de la biblioteca, y vuelve a intentarlo. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Una carpeta ocupa la ruta del archivo de bloqueo de mantenimiento: %1. Renómbrala o muévela antes de reintentar. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + No se pudo crear el archivo de bloqueo de la biblioteca: %1. Comprueba los permisos de escritura de la carpeta de datos de la biblioteca. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Falta la carpeta de datos de la biblioteca: %1. Restaura la carpeta o vuelve a crear la biblioteca antes de actualizarla. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + La biblioteca tiene un bloqueo de mantenimiento cuyo propietario no se pudo verificar (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Otra operación de mantenimiento está usando esta biblioteca (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + No se pudo crear el archivo de bloqueo de la biblioteca: %1. Comprueba el espacio disponible y el acceso a la carpeta de datos de la biblioteca. + + LibraryManagementCoordinator - + Error opening the library - Error abriendo la biblioteca + Error al abrir la biblioteca - + Error creating the library - Errar creando la biblioteca + Error al crear la biblioteca - + Error updating the library - Error actualizando la biblioteca + Error al actualizar la biblioteca + + + + + XML information rescan + Reescanear información XML + + + + Restore recovery + Recuperación de restauración + + + + Library upgrade + Actualizar biblioteca + + + + Library maintenance + Mantenimiento de la biblioteca + + + + Recover library access + Recuperar acceso a la biblioteca LibraryWindow - + The selected folder doesn't contain any library. La carpeta seleccionada no contiene ninguna biblioteca. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? Esta biblioteca fue creada con una versión anterior de YACReaderLibrary. Es necesario que se actualice. ¿Deseas hacerlo ahora? - Remove and delete metadata - Eliminar y borrar metadatos - - - + Old library Biblioteca antigua - + This library was created with a newer version of YACReaderLibrary. Download the new version now? Esta biblioteca fue creada con una versión más nueva de YACReaderLibrary. ¿Deseas descargar la nueva versión ahora? - + Library '%1' is no longer available. Do you want to remove it? La biblioteca '%1' no está disponible. ¿Deseas eliminarla? - - Do you want remove - ¿Deseas eliminar la biblioteca + + Do you want to remove the library '%1'? + ¿Deseas eliminar la biblioteca '%1'? - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? La biblioteca '%1' ha sido creada con una versión más antigua de YACReaderLibrary y debe ser creada de nuevo. ¿Deseas crear la biblioteca ahora? - + Library not available Biblioteca no disponible - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. - Ha habido algún problema intentando borrar los cómics selecionados. Por favor, verifica los permisos de escritura en los arhicovs seleccionados o los directorios que los conienen. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + Ha habido un problema al intentar borrar los cómics seleccionados. Por favor, verifica los permisos de escritura en los archivos seleccionados o en la carpeta que los contiene. - + YACReader Library Biblioteca YACReader - + Update needed Se necesita actualizar - + Library name already exists Ya existe el nombre de la biblioteca - + There is another library with the name '%1'. Hay otra biblioteca con el nombre '%1'. - + Download new version Descargar la nueva versión - + Delete comics Borrar cómics - + All the selected comics will be deleted from your disk. Are you sure? Todos los cómics seleccionados serán borrados de tu disco. ¿Estás seguro? - + Library not found Biblioteca no encontrada - + Unable to delete No se ha podido borrar - - library? - ? - - - + Are you sure? ¿Estás seguro? + Delete folder Borrar carpeta - + Upgrade failed La actualización falló - + There were errors during library upgrade in: Hubo errores durante la actualización de la biblioteca en: - + Copying comics... Copiando cómics... - + Moving comics... Moviendo cómics... @@ -1169,13 +1902,13 @@ No folder selected - No has selecionado ninguna carpeta + No has seleccionado ninguna carpeta - Please, select a folder first + Please select a folder first Por favor, selecciona una carpeta primero @@ -1195,82 +1928,111 @@ - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. Se produjo un problema al intentar eliminar las carpetas seleccionadas. Por favor, verifica los permisos de escritura y asegúrate de que no haya aplicaciones usando estas carpetas o alguno de los archivos contenidos en ellas. - + + Edit smart list... + Editar lista inteligente... + + + + Delete smart list + Eliminar lista inteligente + + + + + + New folder... + Nueva carpeta... + + + + + + Move to folder... + Mover a carpeta... + + + + Rename folder... + Renombrar carpeta... + + + + Match to library comics... + Emparejar con cómics de la biblioteca... + + + + Track missing issues... + Registrar números ausentes... + + + + Export reading list... + Exportar lista de lectura... + + + Rename or organize files Renombrar u organizar archivos - + Set the type of the selected comics Establecer el tipo de los cómics seleccionados - + + Save as smart list… + Guardar como lista inteligente… + + + Search filters Filtros de búsqueda - + Unread No leído - + In progress En curso - + Highly rated Con valoración alta - + Recently added Añadido recientemente - + Search syntax… Sintaxis de búsqueda… - - A repair of this library is already running (%1). Wait for it to finish. - Ya se está ejecutando una reparación de esta biblioteca (%1). Espere a que finalice. - - - - The library is locked by a repair that did not finish. - La biblioteca está bloqueada por una reparación que no finalizó. - - - - The library is locked by a repair started by %1. - La biblioteca está bloqueada por una reparación iniciada por %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Si está seguro de que no se está ejecutando ninguna otra reparación, se puede eliminar el bloqueo. ¿Eliminar el bloqueo y continuar? - - - + Package operation failed Error en la operación de empaquetado - + The covers package operation could not be completed. No se ha podido completar la operación con el paquete de portadas. - + Restore recovery failed Error al recuperar la restauración @@ -1321,71 +2083,65 @@ Carpeta: %1 No se ha podido actualizar la base de datos de la biblioteca ni deshacer el cambio de nombre de la carpeta en el disco. Ahora hay que actualizar la biblioteca a mano. - + Save covers Guardar portadas - + You are adding too many libraries. Estás añadiendo demasiadas bibliotecas. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. Estás añadiendo demasiadas bibliotecas. -Probablemente solo necesites una biblioteca en la carpeta principal de tus cómics, puedes explorar cualquier subcarpeta utilizando la sección de carpetas en la barra lateral izquierda. +Probablemente solo necesites una biblioteca en la carpeta principal de tus cómics. Puedes explorar cualquier subcarpeta utilizando la sección de carpetas en la barra lateral izquierda. -YACReaderLibrary no te detendrá de crear más bibliotecas, pero deberías mantener el número de bibliotecas bajo control. +YACReaderLibrary no te impedirá crear más bibliotecas, pero deberías mantener el número de bibliotecas bajo control. - - + + YACReader library database (*.ydb) Base de datos de biblioteca de YACReader (*.ydb) - + The library database backup was created at: %1 La copia de seguridad de la base de datos de la biblioteca se creó en: %1 - + Unable to create the library database backup: %1 No se pudo crear la copia de seguridad de la base de datos de la biblioteca: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? Cierra YACReaderLibraryServer y cualquier otra aplicación YACReader que esté usando esta biblioteca antes de restaurarla. ¿Quieres continuar? - + Restoring library database... Restaurando la base de datos de la biblioteca... - + The current library database is invalid. Restore the selected backup anyway? La base de datos actual de la biblioteca no es válida. ¿Quieres restaurar de todos modos la copia seleccionada? - - - The library maintenance lock may be stale. Remove it and retry? - El bloqueo de mantenimiento de la biblioteca puede estar obsoleto. ¿Quieres eliminarlo y volver a intentarlo? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1394,71 +2150,77 @@ Restart YACReaderLibrary before attempting recovery again. Reinicia YACReaderLibrary antes de volver a intentar la recuperación. - + The library database was restored successfully. Update the library now? La base de datos de la biblioteca se restauró correctamente. ¿Quieres actualizar la biblioteca ahora? - + Library database damaged Base de datos de la biblioteca dañada - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. La base de datos de la biblioteca '%1' está dañada, por lo que las actualizaciones, el mantenimiento y las copias de seguridad habituales no están disponibles. YACReader puede intentar reparar la base de datos. Es posible que algunos datos dañados no se puedan recuperar. Las copias de seguridad existentes no se modificarán. - + Attempt repair Intentar reparar - + Restore a backup... Restaurar una copia de seguridad... - + Repairing library database... Reparando la base de datos de la biblioteca... - - + + + Library database repair Reparación de la base de datos de la biblioteca - - Another maintenance operation is currently using this library. Try again after it finishes. - Otra operación de mantenimiento está usando esta biblioteca. Vuelve a intentarlo cuando termine. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + ¿Comprobar la base de datos de la biblioteca '%1' e intentar repararla si es necesario? Las copias de seguridad existentes no se modificarán. + + + + Check and repair + Comprobar y reparar - + The library database is already valid. La base de datos de la biblioteca ya es válida. - + Library database repaired Base de datos de la biblioteca reparada - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 La base de datos de la biblioteca se reparó reconstruyendo sus índices. El original dañado se conservó en: %1 - + Library database rebuilt Base de datos de la biblioteca reconstruida - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1469,7 +2231,7 @@ Update the library now? ¿Quieres actualizar la biblioteca ahora? - + The damaged original was preserved at: @@ -1480,12 +2242,12 @@ El original dañado se conservó en: %1 - + Library database repair failed Error al reparar la base de datos de la biblioteca - + The library database could not be repaired: %1%2 @@ -1496,61 +2258,102 @@ You can restore a backup from the Library menu or recreate the library. Puedes restaurar una copia de seguridad desde el menú Biblioteca o volver a crear la biblioteca. - + Remove and delete metadata and backups Eliminar y borrar metadatos y copias de seguridad - + Library info Información de la biblioteca - + Assign comics numbers Asignar números a los cómics - - Assign numbers starting in: + + Assign numbers starting at: Asignar números comenzando en: - + Invalid image - Imagen inválida + Imagen no válida - + The selected file is not a valid image. El archivo seleccionado no es una imagen válida. - + + Error saving cover - Error guardando portada + Error al guardar la portada - + + There was an error saving the cover image. - Hubo un error guardando la image de portada. + Hubo un error al guardar la imagen de portada. - + + + Match to library comic + Emparejar con un cómic de la biblioteca + + + + This also gives the comic to entries in %n other list(s): %1 + + También se asigna el cómic a entradas de %n lista más: %1 + También se asigna el cómic a entradas de %n listas más: %1 + + + + + The comic could not be matched. Nothing was changed. + No se pudo emparejar el cómic. No se modificó nada. + + + + The selected missing comics will be removed from this folder. Are you sure? + Los cómics ausentes seleccionados se quitarán de esta carpeta. ¿Estás seguro? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + Los cómics seleccionados se eliminarán del disco y los cómics ausentes seleccionados se quitarán de esta carpeta. ¿Estás seguro? + + + + Remove the comics from Reading? Their reading progress will be reset. + ¿Quitar los cómics de En lectura? Se restablecerá su progreso de lectura. + + + + The comics will be removed from Favorites. Are you sure? + Los cómics se quitarán de Favoritos. ¿Estás seguro? + + + Remove comics Eliminar cómics - + Comics will only be deleted from the current label/list. Are you sure? - Los cómics sólo se eliminarán de la etiqueta/lista actual. ¿Estás seguro? + Los cómics solo se eliminarán de la etiqueta/lista actual. ¿Estás seguro? - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1569,7 +2372,7 @@ Archivos ausentes: %3 Open an existing library - Abrir una biblioteca existente + Abrir una biblioteca existente @@ -1635,433 +2438,504 @@ Archivos ausentes: %3 + Repair library database... + Reparar base de datos de la biblioteca... + + + + Check the current library database and attempt to repair any damage + Comprobar la base de datos de la biblioteca actual e intentar reparar los daños + + + + Recover library access... + Recuperar acceso a la biblioteca... + + + + Recover access after an interrupted library maintenance operation + Recuperar el acceso tras una operación de mantenimiento de la biblioteca interrumpida + + + Repair covers and comic info Reparar portadas e información de cómics - + Retry comics with missing covers or incomplete information Volver a procesar cómics con portadas ausentes o información incompleta - + Rename library Renombrar biblioteca - + Rename current library Renombrar la biblioteca seleccionada - + Remove library Eliminar biblioteca - + Remove current library from your collection Eliminar biblioteca de la colección - + Rescan library for XML info Volver a escanear la biblioteca en busca de información XML - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Intenta encontrar información XML incrustada en los archivos de cómic. Solo necesitas hacer esto si la biblioteca fue creada con la versión 9.8.2 o versiones anteriores o si estás utilizando software de terceros para incrustar información XML en los archivos. - + Open library folder... Abrir carpeta de la biblioteca... - + Open the root folder of the current library Abrir la carpeta raíz de la biblioteca actual - + Show library info Mostrar información de la biblioteca - + Show information about the current library Mostrar información de la biblioteca actual - + Open current comic Abrir cómic actual - - Open current comic on YACReader + + Open current comic in YACReader Abrir el cómic actual en YACReader - + Save selected covers to... Guardar las portadas seleccionadas en... - + Save covers of the selected comics as JPG files Guardar las portadas de los cómics seleccionados como archivos JPG - - + + Set as read Marcar como leído - + Set comic as read Marcar cómic como leído - - + + Set as unread Marcar como no leído - + Set comic as unread Marcar cómic como no leído - - + + manga - historieta manga + manga - + Set issue as manga Marcar número como manga - - + + comic cómic - + Set issue as normal Marcar número como cómic - + western manga manga occidental - + Set issue as western manga Marcar número como manga occidental - - + + web comic cómic web - + Set issue as web comic Marcar número como cómic web - - + + yonkoma tira yonkoma - + Set issue as yonkoma Marcar número como yonkoma - + Show/Hide marks Mostrar/Ocultar marcas - + Show or hide read marks - Mostrar u ocultar marcas + Mostrar u ocultar marcas de lectura - + Show/Hide recent indicator Mostrar/Ocultar el indicador reciente - + Show or hide recent indicator Mostrar o ocultar el indicador reciente - - + + Fullscreen mode on/off - Modo a pantalla completa on/off + Modo de pantalla completa activado/desactivado - + Help, About YACReader - Ayuda, A cerca de... YACReader + Ayuda, Acerca de YACReader - + Add new folder Añadir carpeta - + Add new folder to the current library Añadir carpeta a la biblioteca actual - + Rename folder Renombrar carpeta - + Rename the current folder on disk and in the library Renombrar la carpeta actual en el disco y en la biblioteca - + Delete folder Borrar carpeta - + Delete current folder from disk Borrar carpeta actual del disco - + Select root node Seleccionar el nodo raíz - + Expand all nodes Expandir todos los nodos - + Collapse all nodes Contraer todos los nodos - + Show options dialog Mostrar opciones - + Show comics server options dialog Mostrar el diálogo de opciones del servidor de cómics - - + + Change between comics views Cambiar entre vistas de cómics - + + Grid view + Vista en cuadrícula + + + + Flow view + + + + + Info view + + + + Open folder... Abrir carpeta... - - + + Rename files... Organize files Renombrar archivos... - - + + Organize into folders... Organizar en carpetas... - + Set as uncompleted Marcar como incompleto - + Set as completed Marcar como completo - + Set custom cover Establecer portada personalizada - + Delete custom cover Eliminar portada personalizada - + western manga (left to right) - manga occidental (izquierda a derecha) + manga occidental (de izquierda a derecha) - + Open containing folder... Abrir carpeta contenedora... - Reset comic rating - Reseteal cómic rating + + New reading list + Nueva lista de lectura - + Select all comics Seleccionar todos los cómics - + Edit Editar - + Assign current order to comics Asignar el orden actual a los cómics - + Update cover Actualizar portada - + Delete selected comics Borrar los cómics seleccionados - + Delete metadata from selected comics Borrar metadatos de los cómics seleccionados - + Download tags from Comic Vine Descargar etiquetas de Comic Vine - + Focus search line - Selecionar el campo de búsqueda + Seleccionar el campo de búsqueda - + Focus comics view - Selecionar la vista de cómics + Seleccionar la vista de cómics - + Edit shortcuts Editar atajos - + &Quit &Salir - + Update folder Actualizar carpeta - + Update current folder Actualizar carpeta actual - + Scan legacy XML metadata - Escaneal metadatos XML - - - - Add new reading list - Añadir lista de lectura + Escanear metadatos XML heredados - + Add a new reading list to the current library Añadir una nueva lista de lectura a la biblioteca actual - + + New smart list... + Nueva lista inteligente... + + + + Add a saved search to the current library + Añadir una búsqueda guardada a la biblioteca actual + + + + New folder... + Nueva carpeta... + + + + Add a folder for reading lists or smart lists + Añadir una carpeta para listas de lectura o listas inteligentes + + + + Import reading lists... + Importar listas de lectura... + + + + Import CBL reading lists into the current library + Importar listas de lectura CBL a la biblioteca actual + + + + New + Nuevo + + + + Add or import reading lists + Añadir o importar listas de lectura + + + Remove reading list Eliminar lista de lectura - + Remove current reading list from the library Eliminar la lista de lectura actual de la biblioteca - + Add new label Añadir etiqueta - + Add a new label to this library Añadir etiqueta a esta biblioteca - + Rename selected list Renombrar la lista seleccionada - + Rename any selected labels or lists Renombrar las etiquetas o listas seleccionadas - + Add to... Añadir a... - + Favorites Favoritos - + Add selected comics to favorites list Añadir cómics seleccionados a la lista de favoritos - - + + Reset rating Restablecer valoración @@ -2076,12 +2950,12 @@ Archivos ausentes: %3 manga - historieta manga + manga western manga (left to right) - manga occidental (izquierda a derecha) + manga occidental (de izquierda a derecha) @@ -2090,91 +2964,104 @@ Archivos ausentes: %3 - 4koma (top to botom) + 4koma (top to bottom) 4koma (de arriba a abajo) - - - + + + Set type Establecer tipo - + Library - Librería + Biblioteca - + Folder Carpeta - + Comic Cómic - + + Match to library comic... + Emparejar con un cómic de la biblioteca... + + + + + + + Track missing issues... + Registrar números ausentes... + + + Open folder... Abrir carpeta... - + Update folder Actualizar carpeta - + Rename folder Renombrar carpeta - + Rename files... Renombrar archivos... - + Organize into folders... Organizar en carpetas... - + Rescan library for XML info Volver a escanear la biblioteca en busca de información XML - + Set as uncompleted Marcar como incompleto - + Set as completed Marcar como completo - + Set as read Marcar como leído - - + + Set as unread Marcar como no leído - + Set custom cover Establecer portada personalizada - - + + Delete custom cover Eliminar portada personalizada @@ -2215,11 +3102,57 @@ Archivos ausentes: %3 LocalComicListModel - + file name nombre de archivo + + MoveToFolderDialog + + + Move to folder + Mover a carpeta + + + + Top level + Nivel superior + + + + Move "%1" to: + Mover "%1" a: + + + + NewListFolderDialog + + + New folder + Nueva carpeta + + + + Reading lists + Listas de lectura + + + + Smart lists + Listas inteligentes + + + + Folder name: + Nombre de la carpeta: + + + + For: + Para: + + NoLibrariesWidget @@ -2234,8 +3167,8 @@ Archivos ausentes: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>Puedes crear una biblioteca en cualquier carpeta, YACReaderLibrary importará todos las carpetas y cómics de esa carpeta. Si has creado alguna biblioteca anteriormente, puedes abrirla sin volver a crearla.</p><p>No olvides que puedes usar YACReader como una aplicación independiente para leer los cómics en tu ordenador.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Puedes crear una biblioteca en cualquier carpeta; YACReaderLibrary importará todos los cómics y carpetas de esa carpeta. Si has creado alguna biblioteca anteriormente, puedes abrirla sin volver a crearla.</p><p>No olvides que puedes usar YACReader como una aplicación independiente para leer los cómics en tu ordenador.</p> @@ -2290,7 +3223,7 @@ Archivos ausentes: %3 - Start into the system tray + Start in the system tray Comenzar en la bandeja de sistema @@ -2306,7 +3239,7 @@ Archivos ausentes: %3 ComicInfo.xml legacy support - Soporte para ComicInfo.xml + Soporte heredado de ComicInfo.xml @@ -2332,7 +3265,7 @@ Archivos ausentes: %3 Clear - Borrar + Limpiar @@ -2387,7 +3320,7 @@ Archivos ausentes: %3 daily - dirariamente + diariamente @@ -2401,10 +3334,10 @@ Archivos ausentes: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2445,7 +3378,7 @@ Para detener una actualización automática, toca en el indicador de carga junto - Restore defautls + Restore defaults Restaurar valores predeterminados @@ -2461,7 +3394,7 @@ Para detener una actualización automática, toca en el indicador de carga junto Display current comic banner - Mostar el báner del cómic actual + Mostrar el banner del cómic actual @@ -2502,7 +3435,7 @@ Para detener una actualización automática, toca en el indicador de carga junto General - Opciones generales + General @@ -2513,12 +3446,93 @@ Para detener una actualización automática, toca en el indicador de carga junto OrganizeFiles - + + + Unknown Series + Serie desconocida + + + + series + serie + + + + Unknown Publisher + Editorial desconocida + + + + publisher + editorial + + + + title + título + + + + Unknown Comic + Cómic desconocido + + + + Series #Number - Title + Serie #Número - Título + + + + Series #Number + Serie #Número + + + + Series #Number (of Count) + Serie #Número (de Total) + + + + Number - Title + Número - Título + + + + Series #Number (Year) + Serie #Número (Año) + + + + Publisher / Series / Series #Number - Title + Editorial / Serie / Serie #Número - Título + + + + Publisher / Imprint / Series / Series #Number - Title + Editorial / Sello editorial / Serie / Serie #Número - Título + + + + Series / Series #Number + Serie / Serie #Número + + + + Publisher / Series / Series #Number (Year) + Editorial / Serie / Serie #Número (Año) + + + + Series / original file name + Serie / nombre de archivo original + + + Renamed, %1 is already in use Renombrado, %1 ya está en uso - + Missing metadata: %1 Faltan metadatos: %1 @@ -2527,62 +3541,82 @@ Para detener una actualización automática, toca en el indicador de carga junto %1 could not be created No se ha podido crear %1 + + + %1 (copy also failed: %2) + %1 (también falló la copia: %2) + + + + the copy did not have the same size as the original + la copia no tenía el mismo tamaño que el original + + + + the original could not be deleted after it was copied + no se pudo eliminar el original después de copiarlo + + + + The destination folder could not be created. + No se pudo crear la carpeta de destino. + + + + %1 (the file was left at %2) + %1 (el archivo quedó en %2) + OrganizeFilesCoordinator - - + + Organize files Organizar archivos - + This folder does not contain any comics. Esta carpeta no contiene ningún cómic. - - This library is busy: %1 - Esta biblioteca está ocupada: %1 - - - + the library database could not be opened no se ha podido abrir la base de datos de la biblioteca - + the library database could not be locked for writing no se ha podido bloquear la base de datos de la biblioteca para escritura - + a folder entry could not be restored no se ha podido restaurar una entrada de carpeta - + a comic entry could not be updated no se ha podido actualizar una entrada de cómic - + the library database could not be saved: %1 no se ha podido guardar la base de datos de la biblioteca: %1 - + the record of the last organize run could not be read no se ha podido leer el registro de la última organización - + the folder %1 could not be created no se ha podido crear la carpeta %1 - + %n file(s) could not be moved back no se ha podido devolver %n archivo a su sitio @@ -2592,10 +3626,6 @@ Para detener una actualización automática, toca en el indicador de carga junto OrganizeFilesDialog - - Format: - Formato: - Organize files @@ -2727,19 +3757,11 @@ Para detener una actualización automática, toca en el indicador de carga junto Cancel Cancelar - - Copy the list - Copiar la lista - Undo Deshacer - - Close - Cerrar - Copy failure details @@ -3158,274 +4180,386 @@ Para detener una actualización automática, toca en el indicador de carga junto PropertiesDialog - + Day: Día: - + Plot Argumento - + Size: Tamaño: - + Year: Año: - + Inker(s): Entintador(es): - + Publishing Publicación - + Publisher: Editorial: - + General info Información general - + Color/BW: Color/BN: - + Edit selected comics information Editar la información de los cómics seleccionados - + Penciller(s): - Dibujant(es): + Dibujante(s): - + Colorist(s): - Color: + Colorista(s): - + Genre: Género: - + Notes Notas - + Load previous page as cover Cargar página anterior como portada - + Load next page as cover Cargar página siguiente como portada - + Reset cover to the default image Restaurar la portada por defecto - + Load custom cover image Cargar portada personalizada - + Series: Serie: - + + e.g. 1-5, 8, 0.5, Annual 1 + p. ej. 1-5, 8, 0.5, Annual 1 + + + + Issue number: Número: - + alt. number: número alternativo: - + Alternate series: Serie alternativa: - + Series Group: Grupo de series: - + Editor(s): Editor(es): - + Imprint: Sello: - + Month: Mes: - + Type: Tipo: - + Language (ISO): Idioma (ISO): - + Teams: Equipos: - + Locations: Lugares: - + Main character or team: Personaje o equipo principal: - + Review: Reseña: - + Notes: Notas: - + + + Track missing issues in "%1" + Registrar números ausentes en "%1" + + + + Issues: + Números: + + + + Track + Registrar + + + + At most %1 issues can be tracked at once. + Se pueden registrar como máximo %1 números a la vez. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Comprueba los números: los rangos deben ser ascendentes y no puede haber partes vacías entre comas. + + + + No new issues to track. + No hay números nuevos que registrar. + + + + %n issue(s) will be tracked: %1. + + Se registrará %n número: %1. + Se registrarán %n números: %1. + + + + + That is a lot of issues; check the numbers. + Son muchos números; comprueba la numeración. + + + + %n issue(s) already in the folder. + + %n número ya está en la carpeta. + %n números ya están en la carpeta. + + + + + %n repeated issue(s). + + %n número repetido. + %n números repetidos. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Números no registrados + + + + The missing issues could not be tracked. + No se pudieron registrar los números ausentes. + + + + Comic skipped + Cómic omitido + + + + A comic was removed from the library or now has a different file, so it is skipped. + Un cómic se quitó de la biblioteca o ahora tiene un archivo distinto, por lo que se omite. + + + + + Changes not saved + Cambios no guardados + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Un cómic que estás editando se quitó de la biblioteca o ahora tiene un archivo distinto. Tus cambios no se guardaron. + + + + The changes could not be saved. + No se pudieron guardar los cambios. + + + Invalid cover - Portada inválida + Portada no válida - + The image is invalid. La imagen no es válida. - + Synopsis: Sinopsis: - + Title: Título: - + Not found No encontrado - + Characters: Personajes: - + Authors Autores - + Age rating: - Casificación edades: + Clasificación de edad: - + Story arc: Arco argumental: - + Writer(s): Guionista(s): - + Tags: Etiquetas: - + Comic not found. You should update your library. - Cómic no encontrado. Deberias actualizar tu biblioteca. + Cómic no encontrado. Deberías actualizar tu biblioteca. - + Edit comic information - Editar la información del cócmic + Editar la información del cómic - + Cover page Página de portada - + Cover Artist(s): - Artista(s) portada: + Artista(s) de portada: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> ver </a> + Enlace de Comic Vine: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> ver </a> - + Volume: Volumen: - + Format: Formato: - + Letterer(s): Rotulista(s): - - + + of: de: - + Arc number: Número de arco: @@ -3435,20 +4569,20 @@ Para detener una actualización automática, toca en el indicador de carga junto 7z lib not found - 7z lib no encontrado + Biblioteca 7z no encontrada unable to load 7z lib from ./utils - imposible cargar 7z lib de ./utils + no se puede cargar la biblioteca 7z desde ./utils - + Select custom cover Seleccionar portada personalizada - + Images (%1) Imágenes (%1) @@ -3481,31 +4615,166 @@ Para detener una actualización automática, toca en el indicador de carga junto ReadingListManagementCoordinator - + Add new reading lists Añadir nuevas listas de lectura - - + + List name: Nombre de la lista: - + + Import reading lists + Importar listas de lectura + + + + Reading lists (*.cbl *.json);;All files (*) + Listas de lectura (*.cbl *.json);;Todos los archivos (*) + + + + New smart list + Nueva lista inteligente + + + + + The smart list could not be saved. + No se pudo guardar la lista inteligente. + + + + Edit smart list + Editar lista inteligente + + + + Delete folder + Borrar carpeta + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + ¿Eliminar la carpeta "%1" y todo su contenido? Se eliminarán sus carpetas y listas, pero no tus archivos de cómics. + + + Delete list/label Eliminar lista/etiqueta - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? El elemento seleccionado se eliminará, tus cómics o carpetas NO se eliminarán de tu disco. ¿Estás seguro? - + Rename list name Renombrar lista + + + Rename folder + Renombrar carpeta + + + + + Folder name: + Nombre de la carpeta: + + + + + + New folder + Nueva carpeta + + + + + The folder could not be created. + No se pudo crear la carpeta. + + + + Reading list + Lista de lectura + + + + CBL reading list (*.cbl) + Lista de lectura CBL (*.cbl) + + + + JSON reading list (*.json) + Lista de lectura JSON (*.json) + + + + Web page (*.html) + Página web (*.html) + + + + + Export reading list + Exportar lista de lectura + + + + Exporting reading list... + Exportando lista de lectura... + + + + The file could not be written. + No se pudo escribir el archivo. + + + + Move to folder + Mover a carpeta + + + + "%1" could not be moved. + No se pudo mover "%1". + + + + ReadingListPage + + + %n comic(s) + + %n cómic + %n cómics + + + + + %n missing + + %n no encontrado + %n no encontrados + + + + + Missing + Ausente + + + + Exported from YACReader on %1 + Exportado desde YACReader el %1 + RenameLibraryDialog @@ -3526,16 +4795,16 @@ Para detener una actualización automática, toca en el indicador de carga junto - New Library Name : - Nuevo nombre de la biblioteca : + New Library Name: + Nuevo nombre de la biblioteca: ScraperResultsPaginator - Number of %1 found : %2 - Número de %1 encontrados : %2 + Number of %1 found: %2 + Número de %1 encontrados: %2 @@ -3545,8 +4814,8 @@ Para detener una actualización automática, toca en el indicador de carga junto - Number of volumes found : %1 - Número de volúmenes encontrados : %1 + Number of volumes found: %1 + Número de volúmenes encontrados: %1 @@ -3764,7 +5033,7 @@ Para detener una actualización automática, toca en el indicador de carga junto Whether the comic is in color - Si el cómic es en color + Si el cómic es a color @@ -3828,7 +5097,7 @@ Para detener una actualización automática, toca en el indicador de carga junto Please provide some additional information for this comic. Please provide some additional information. - Por favor, proporciona alguna información adicional para éste cómic. + Por favor, proporciona alguna información adicional para este cómic. @@ -3908,6 +5177,11 @@ Para detener una actualización automática, toca en el indicador de carga junto Just start typing. Plain text search across all metadata. Solo tienes que empezar a escribir. El texto simple busca en todos los metadatos. + + + %1 -> search for `%1` in any database field + %1 -> buscar `%1` en cualquier campo de la base de datos + 1. Search everywhere @@ -4003,7 +5277,9 @@ Para detener una actualización automática, toca en el indicador de carga junto Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Consejos: los espacios funcionan como AND y las búsquedas no distinguen entre mayúsculas y minúsculas. Usa comillas para incluir espacios en un valor. + Consejos: +Los espacios funcionan como AND y las búsquedas no distinguen entre mayúsculas y minúsculas. +Usa comillas para incluir espacios en un valor. @@ -4141,7 +5417,7 @@ Use quotes to include spaces in a value. Please provide some additional information. - Por favor, proporciona alguna informacion adicional. + Por favor, proporciona alguna información adicional. @@ -4151,7 +5427,7 @@ Use quotes to include spaces in a value. Use exact match search. Disable if you want to find volumes that match some of the words in the name. - Usar búsqueda exacta. Desactívala si queires buscar volúmenes que coincidan con algunas de las palabras del nombre. + Usar búsqueda exacta. Desactívala si quieres encontrar volúmenes que coincidan con algunas palabras del nombre. @@ -4178,7 +5454,7 @@ Use quotes to include spaces in a value. - Please, select the right comic info. + Please select the right comic info. Por favor, selecciona la información correcta. @@ -4191,8 +5467,8 @@ Use quotes to include spaces in a value. - Please, select the right series for your comic. - Por favor, seleciona la serie correcta para tu cómic. + Please select the right series for your comic. + Por favor, selecciona la serie correcta para tu cómic. @@ -4225,54 +5501,59 @@ Use quotes to include spaces in a value. Unknown - Desconocido + Desconocido %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 desconocidas) %1 series - + %1 series - + %1 comics selected %n comic(s) selected - %1 cómics seleccionados + %1 cómics seleccionados - + Read - Leído + Leído - + In progress - En curso + En curso - + Unread - No leído + No leído - + Total pages - + Páginas totales - + Total size - + Tamaño total - + + Missing + Ausente + + + Series - Serie + Serie @@ -4280,7 +5561,7 @@ Use quotes to include spaces in a value. no - No + no @@ -4289,7 +5570,7 @@ Use quotes to include spaces in a value. - You are trying to get information for various comics at once, are they part of the same series? + You are trying to get information for various comics at once. Are they part of the same series? Estás intentando obtener información de varios cómics a la vez, ¿son parte de la misma serie? @@ -4346,59 +5627,75 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader está disponible para iOS y Android. Descúbrelo para <a href='https://ios.yacreader.com'>iOS</a> o <a href='https://android.yacreader.com'>Android</a>. - - enable the server - activar el servidor - Set port set port Establecer puerto + + + SmartListDialog + + + New smart list + Nueva lista inteligente + - Server connectivity information - Infomación de conexión del servidor + + Edit smart list + Editar lista inteligente - Scan it! - ¡Escaneálo! + + Search filters and syntax + Filtros y sintaxis de búsqueda - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader está disponible para iOS y Android.<br/> Descúbrela para <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a>o <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Name: + Nombre: - Choose an IP address - Elige una dirección IP + + Search: + Búsqueda: SortVolumeComics - + remove selected comics eliminar cómics seleccionados - + sort comics to match comic information ordena los cómics para coincidir con la información - + restore all removed comics restaurar todos los cómics eliminados + + + Add the %n other issue(s) of this volume as missing comics + + Añadir el otro número de este volumen como cómic ausente (%n) + Añadir los otros %n números de este volumen como cómics ausentes + + - + issues números - - Please, sort the list of comics on the left until it matches the comics' information. - Por favor, ordena la lista de cómics en la izquiera hasta que coincida con la información adecuada. + + Please sort the list of comics on the left until it matches the comics' information. + Por favor, ordena la lista de cómics de la izquierda hasta que coincida con la información de los cómics. @@ -4421,7 +5718,7 @@ Use quotes to include spaces in a value. i - ? + i @@ -4446,7 +5743,7 @@ Use quotes to include spaces in a value. Light - Luz + Claro @@ -4456,7 +5753,7 @@ Use quotes to include spaces in a value. ID: - IDENTIFICACIÓN: + ID: @@ -4516,7 +5813,7 @@ Use quotes to include spaces in a value. true - verdadero + true @@ -4524,7 +5821,7 @@ Use quotes to include spaces in a value. false - falso + false @@ -4620,8 +5917,16 @@ Use quotes to include spaces in a value. - Updating.... - Actualizado... + Updating... + Actualizando... + + + + ViewSelector + + + View + @@ -4647,7 +5952,7 @@ Use quotes to include spaces in a value. publisher - editor + editorial @@ -4685,7 +5990,7 @@ Use quotes to include spaces in a value. Show advanced settings - Opciones avanzadas + Mostrar opciones avanzadas @@ -4715,7 +6020,7 @@ Use quotes to include spaces in a value. Zoom - Ampliaci?n + Zoom @@ -4755,11 +6060,11 @@ Use quotes to include spaces in a value. High Performance - Alto rendimiento + Rendimiento alto - Use VSync (improve the image quality in fullscreen mode, worse performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) Usar VSync (mejora la calidad de imagen en pantalla completa, peor rendimiento) @@ -4783,7 +6088,7 @@ Use quotes to include spaces in a value. YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary se continuará ejecutando en la bandeja del sistema. Para cerrar el programa elige <b>Cerrar</b> en el menú contextual del icono de la aplicación en la bandeja del sistema. + YACReaderLibrary seguirá ejecutándose en la bandeja del sistema. Para cerrar el programa, elige <b>Salir</b> en el menú contextual del icono de la bandeja del sistema. @@ -4799,6 +6104,44 @@ Use quotes to include spaces in a value. Versiones anteriores + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Falta la base de datos de la biblioteca. Restaura una copia de seguridad o vuelve a crear la biblioteca antes de reescanear la información XML. + + + + Unable to load the archive backend: %1 + No se pudo cargar el motor de archivos comprimidos: %1 + + + + Unable to start an XML rescan transaction. + No se pudo iniciar una transacción de reescaneo XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + La carpeta seleccionada ya no está en esta biblioteca. Vuelve a cargar la biblioteca antes de reescanear. + + + + Unable to read comics for the XML rescan. + No se pudieron leer los cómics para el reescaneo XML. + + + + Unable to commit XML metadata changes. + No se pudieron confirmar los cambios en los metadatos XML. + + + + Unable to update XML metadata for %1. + No se pudieron actualizar los metadatos XML de %1. + + YACReaderFieldEdit @@ -4808,7 +6151,7 @@ Use quotes to include spaces in a value. - + Click to overwrite Clic para sobrescribir @@ -4821,10 +6164,12 @@ Use quotes to include spaces in a value. Restaurar valor por defecto + + - - - + + + Click to overwrite Clic para sobrescribir diff --git a/YACReaderLibrary/yacreaderlibrary_fr.ts b/YACReaderLibrary/yacreaderlibrary_fr.ts index d33522341..9e27ff3c6 100644 --- a/YACReaderLibrary/yacreaderlibrary_fr.ts +++ b/YACReaderLibrary/yacreaderlibrary_fr.ts @@ -6,15 +6,75 @@ None - Rien + Aucun AddLabelDialog + + + red + Rouge + + + + orange + Orange + + + + yellow + Jaune + + + + green + Vert + + + + cyan + Cyan + + + + blue + Bleu + + + + violet + Violet + + + + purple + Pourpre + + + + pink + Rose + + + + white + Blanc + + + + light + Clair + + + + dark + Sombre + Choose a color: - Choisissez une couleur: + Choisissez une couleur : @@ -24,7 +84,7 @@ cancel - Annuler + annuler @@ -42,7 +102,7 @@ Add an existing library - Ajouter une librairie existante + Ajouter une bibliothèque existante @@ -51,13 +111,13 @@ - Comics folder : - Dossier des bandes dessinées : + Comics folder: + Dossier des bandes dessinées : - Library name : - Nom de la librairie : + Library name: + Nom de la bibliothèque : @@ -74,8 +134,8 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Avant de pouvoir vous connecter à Comic Vine, vous avez besoin de votre propre clé API. Veuillez en obtenir une gratuitement ici: <a href="http://www.comicvine.com/api/"></a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Avant de pouvoir vous connecter à Comic Vine, vous avez besoin de votre propre clé d'API. Veuillez en obtenir une gratuitement <a href="http://www.comicvine.com/api/">ici</a> @@ -98,7 +158,7 @@ Light - Lumière + Clair @@ -108,7 +168,7 @@ Custom - Coutume + Personnalisé @@ -123,17 +183,17 @@ Light: - Lumière: + Clair : Dark: - Sombre: + Sombre : Custom: - Personnalisation: + Personnalisé : @@ -163,7 +223,7 @@ The current theme JSON could not be loaded. - Le thème actuel JSON n'a pas pu être chargé. + Le JSON du thème actuel n'a pas pu être chargé. @@ -200,103 +260,625 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Masquer Comic Flow + + Find comic › + Trouver une BD › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Copier la bande dessinée... + + Series + Série - Moving comics... - Déplacer la bande dessinée... + + Number + Numéro + + + + Volume + Tome + + + + Year + Année + + + + Format + Format + + + + + ComicVine ID + ID Comic Vine + + + + series and number + série et numéro + + + + series without the volume, and number + série sans le volume et numéro + + + + letters and digits of the series, and number + lettres et chiffres de la série et numéro + + + + series with its year, and number + série avec son année et numéro + + + + volume %1 + volume %1 + + + + Comic in your library + BD dans votre bibliothèque + + + + Missing in your library + Absent de votre bibliothèque + + + + Changed by you. + Modifié par vous. + + + + The entry has this comic. + Cette entrée correspond à cette BD. + + + + Matched by %1. + Correspondance selon %1. + + + + Your library already has this comic as missing. Matched by %1. + Votre bibliothèque contient déjà cette BD comme manquante. Correspondance selon %1. + + + + Possible comic %1 + BD possible %1 + + + + + Changed by you: it is imported as missing. + Modifié par vous : importé comme manquant. + + + + The comics have the same ComicVine ID. + Les BD ont le même ID Comic Vine. + + + + The comic has the same series and number, but its volume or year is different. + La BD a la même série et le même numéro, mais son volume ou son année diffère. + + + + The comics have the same series and number. + Les BD ont la même série et le même numéro. + + + + Values that are different from the file are in amber. + Les valeurs différentes de celles du fichier sont en ambre. + + + + Closest series in your library + Séries les plus proches dans votre bibliothèque + + + + Library + Bibliothèque + + + + Has %1 + Contient %1 + + + + No comic found. Tried: %1. + Aucune BD trouvée. Recherche effectuée : %1. + + + + In the file + Dans le fichier + + + + Entry %1 of %2 + Entrée %1 sur %2 + + + + Use this comic + Utiliser cette BD + + + + Use this missing comic + Utiliser cette BD manquante + + + + CblFindComicPage + + + ‹ All entries + ‹ Toutes les entrées + + + + Previous problem + Problème précédent + + + + Next problem + Problème suivant + + + + Folders + Dossiers + + + + Search the whole library + Rechercher dans toute la bibliothèque + + + + File + Fichier + + + + Series + Série + + + + No. + N° + + + + Volume + Tome + + + + Year + Année + + + + Go to the next problem after I choose + Passer au problème suivant après mon choix + + + + Use this comic + Utiliser cette BD + + + + Cancel + Annuler + + + + Search in this folder + Rechercher dans ce dossier + + + + Entry %1 of %2 + Entrée %1 sur %2 + + + + Missing: no comic found. Browse or search the library. + Manquant : aucune BD trouvée. Parcourez la bibliothèque ou lancez une recherche. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Ambigu : %n BD possible. Parcourez la bibliothèque ou lancez une recherche. + Ambigu : %n BD possibles. Parcourez la bibliothèque ou lancez une recherche. + + + + + Browse or search the library to choose another comic. + Parcourez la bibliothèque ou lancez une recherche pour choisir une autre BD. + + + + Problem %1 of %2 + Problème %1 sur %2 + + + + %n problem(s) + + %n problème + %n problèmes + + + + + + + Library + Bibliothèque + + + + Search results + Résultats de recherche + + + + CblImportDialog + + + Match to library comics — %1 + Associer aux BD de la bibliothèque — %1 + + + + Apply + Appliquer + + + + Close + Fermer + + + + Skip + Ignorer + + + + Import + Importer + + + + &Name: + &Nom : + + + + A reading list with this name already exists. Import creates another one. + Une liste de lecture porte déjà ce nom. L'importation en crée une autre. + + + + Filter entries + Filtrer les entrées + + + + Matching the books of %1 with your library... + Association des livres de %1 à votre bibliothèque... + + + + Matching the entries of %1 with your library... + Association des entrées de %1 à votre bibliothèque... + + + + + Match to library comics + Associer aux BD de la bibliothèque + + + + This also gives comics to entries in %n other list(s): %1 + + Cela associe aussi des BD aux entrées de %n autre liste : %1 + Cela associe aussi des BD aux entrées de %n autres listes : %1 + + + + + The changes could not be applied. Nothing was changed. + Impossible d'appliquer les modifications. Rien n'a été modifié. + + + + The file cannot be opened. + Impossible d'ouvrir le fichier. + + + + The file is damaged (line %1, column %2). + Le fichier est endommagé (ligne %1, colonne %2). + + + + The file is not a reading list. + Le fichier n'est pas une liste de lecture. + + + + The library cannot be read. + Impossible de lire la bibliothèque. + + + + Import reading lists + Importer des listes de lecture + + + + The reading list could not be imported. Nothing was written. + Impossible d'importer la liste de lecture. Rien n'a été écrit. + + + + Import reading lists — %1 (%2 of %3) + Importer des listes de lecture — %1 (%2 sur %3) + + + + Import reading lists — %1 + Importer des listes de lecture — %1 + + + + All (%1) + Toutes (%1) + + + + Matched (%1) + Associées (%1) + + + + Ambiguous (%1) + Ambiguës (%1) + + + + Missing (%1) + Manquantes (%1) + + + + Changed by you (%1) + Modifiées par vous (%1) + + + + %1 of %n have a comic + + %1 sur %n a une BD + %1 sur %n ont une BD + + + + + Find comic... + Trouver une BD... + + + + Use the original match + Utiliser la correspondance d'origine + + + + Make missing + Marquer comme manquant + + + + Import as missing + Importer comme manquant + + + + CblImportModel + + + Matched + Associé + + + + Ambiguous + Ambigu + + + + Missing + Manquant + + + + Changed + Modifié + + + + # + # + + + + In the file + Dans le fichier + + + + Comic in your library + BD dans votre bibliothèque + + + + Status + État + + + + Already missing in your library + Déjà manquant dans votre bibliothèque + + + + %n possible comic(s) + + %n BD possible + %n BD possibles + + + + + Not in library + Absent de la bibliothèque + + + + CblMatchComicDialog + + + Match to library comic + Associer à une BD de la bibliothèque + + + + Loading the library... + Chargement de la bibliothèque... + + + + The library cannot be read. + Impossible de lire la bibliothèque. + + + + ClassicComicsView + + + Hide comic flow + Masquer Comic Flow ComicInfoView - + b/w n/b - + cover artist dessinateur de couverture - + color couleur - + inker encreur - + penciller dessinateur - + colorist coloriste - + writer scénariste - + Characters Personnages - + Main character or team Personnage principal ou équipe - + Teams Équipes - + Locations - Emplacements + Lieux - + Authors Auteurs - + editor - éditeur + responsable éditorial - + imprint label éditorial - + Publisher Éditeur - + letterer lettreur @@ -304,28 +886,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader introuvable - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader introuvable. YACReader doit être installé dans le même dossier que YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader introuvable. Il se peut qu'il y ait un problème avec votre installation de YACReader. - + Error Erreur - + Error opening comic with third party reader. Erreur lors de l'ouverture de la bande dessinée avec un lecteur tiers. @@ -333,67 +915,67 @@ ComicModel - + no non - + yes oui - + Read Lu - + Series Série - + Volume Tome - + Story Arc - Arc d'histoire + Arc narratif - + Size Taille - + Pages - Feuilles + Pages - + Title Titre - + Current Page Page en cours - + File Name Nom du fichier - + Publication Date Date de publication - + Rating Note @@ -401,66 +983,110 @@ ComicVineDialog - + back retour - + next suivant - + skip passer - + close fermer - - - Retrieving tags for : %1 - Retrouver les infomartions de: %1 + + + Retrieving tags for: %1 + Récupération des étiquettes pour : %1 - + + Creating missing comic %1 of %2 + Création de la BD manquante %1 sur %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine n'a pas répondu pour %n BD. + Comic Vine n'a pas répondu pour %n BD. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n BD manquante n'a reçu que les données de base du volume. + %n BD manquantes n'ont reçu que les données de base du volume. + + + + + %n missing comic(s) have no cover. + + %n BD manquante n'a pas de couverture. + %n BD manquantes n'ont pas de couverture. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine autorise environ 200 requêtes par heure ; réessayez plus tard pour les BD non mises à jour. + + + + Try again later for the comics that were not updated. + Réessayez plus tard pour les BD non mises à jour. + + + + Comic Vine + Comic Vine + + + Looking for comic... - Vous cherchez une bande dessinée ... + Recherche de la bande dessinée... - + search chercher - - + + comic %1 of %2 - %3 bande dessinée %1 sur %2 - %3 - + %1 comics selected - %1 bande(s) dessinnée(s) sélectionnée(s) + %1 bande(s) dessinée(s) sélectionnée(s) - - Error connecting to ComicVine + + Error connecting to Comic Vine Erreur de connexion à Comic Vine - - - + + + Looking for volume... - Vous cherchez du volume... + Recherche du volume... - + Retrieving volume info... Récupération des informations sur le volume... @@ -478,7 +1104,7 @@ Create new library - Créer une nouvelle librairie + Créer une nouvelle bibliothèque @@ -492,23 +1118,23 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - La création d'une librairie peut prendre quelques minutes. Vous pouvez arrêter le processus et continuer plus tard. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + La création d'une bibliothèque peut prendre quelques minutes. Vous pouvez arrêter le processus et mettre à jour la bibliothèque plus tard pour terminer la tâche. The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - Le chemin sélectionné n'existe pas ou contient un chemin invalide. Assurez-vous d'avoir les droits d'accès à ce dossier + Le chemin sélectionné n'existe pas ou n'est pas valide. Assurez-vous d'avoir les droits d'écriture sur ce dossier - Comics folder : - Dossier des bandes dessinées : + Comics folder: + Dossier des bandes dessinées : - Library Name : - Nom de la librairie : + Library Name: + Nom de la bibliothèque : @@ -519,11 +1145,19 @@ DBHelper - + The folder entry could not be found in the library database. L'entrée du dossier est introuvable dans la base de données de la bibliothèque. + + DelayedProgressDialog + + + Cancel + Annuler + + EditShortcutsDialog @@ -533,8 +1167,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - Pour modifier un raccourci, double-cliquez sur la combinaison de touches et tapez les nouvelles clés. + To change a shortcut, double-click the key combination and type the new keys. + Pour modifier un raccourci, double-cliquez sur la combinaison de touches et saisissez les nouvelles touches. @@ -548,8 +1182,8 @@ - The shortcut "%1" is already assigned to other function - Le raccourci "%1" est déjà affecté à une autre fonction + The shortcut "%1" is already assigned to another function + Le raccourci « %1 » est déjà affecté à une autre fonction @@ -578,7 +1212,15 @@ This label doesn't contain comics yet - Ce dossier ne contient pas encore de bandes dessinées + Cette étiquette ne contient pas encore de bandes dessinées + + + + EmptyListFolderWidget + + + This folder is empty + Ce dossier est vide @@ -599,20 +1241,20 @@ You are not reading anything yet, come on!! - Vous ne lisez rien encore, allez !! + Vous ne lisez encore rien, allez !! There are no recent comics! - Il n'y a pas de BD récente ! + Il n'y a pas de BD récente ! ExportComicsInfoDialog - Output file : - Fichier de sortie : + Output file: + Fichier de sortie : @@ -632,7 +1274,7 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Le chemin sélectionné pour le fichier n'existe pas ou contient un chemin invalide. Assurez-vous d'avoir les droits d'accès à ce dossier + Le chemin sélectionné pour le fichier de sortie n'existe pas ou n'est pas valide. Assurez-vous d'avoir les droits d'écriture sur ce dossier @@ -660,12 +1302,12 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Le chemin sélectionné pour le fichier n'existe pas ou contient un chemin invalide. Assurez-vous d'avoir les droits d'accès à ce dossier + Le chemin sélectionné pour le fichier de sortie n'existe pas ou n'est pas valide. Assurez-vous d'avoir les droits d'écriture sur ce dossier - Output folder : - Dossier de sortie : + Output folder: + Dossier de sortie : @@ -680,40 +1322,33 @@ Destination directory - Répertoire de destination + Dossier de destination FileComic - + 7z not found 7z introuvable - + CRC error on page (%1): some of the pages will not be displayed correctly - Erreur CRC sur la page (%1): certaines pages ne s'afficheront pas correctement + Erreur CRC sur la page (%1) : certaines pages ne s'afficheront pas correctement - + Unknown error opening the file Erreur inconnue lors de l'ouverture du fichier - - + + Format not supported Format non supporté - - FolderContentView - - Continue Reading... - Continuer la lecture... - - FolderInfoView @@ -788,53 +1423,37 @@ GridComicsView - + Show info Afficher les informations - Library - Librairie - - - Folder - Dossier - - - Favorites - Favoris - - - Recently added - Ajoutés récemment - - - + Manga Manga - + Western manga Manga occidental - + Web comic Webcomic - + Yonkoma Yonkoma - + Comic Bande dessinée - + Unknown Inconnu @@ -859,7 +1478,7 @@ About - A propos + À propos @@ -876,8 +1495,8 @@ - Info database location : - Emplacement des infos: + Info database location: + Emplacement de la base de données d'informations : @@ -894,8 +1513,8 @@ ImportLibraryDialog - Destination folder : - Dossier de destination : + Destination folder: + Dossier de destination : @@ -909,18 +1528,18 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) Couvertures de bibliothèque compressées (*.clc) - Package location : - Emplacement : + Package location: + Emplacement du paquet : - Library Name : - Nom de la librairie : + Library Name: + Nom de la bibliothèque : @@ -933,32 +1552,32 @@ stop - Arrêter + arrêter Importing comics - Importation de bande dessinée + Importation des bandes dessinées - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderLibrary est en train de créer une nouvelle librairie.</p><p>La création d'une librairie peut prendre quelques minutes. Vous pouvez arrêter le processus et poursuivre plus tard.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary est en train de créer une nouvelle bibliothèque.</p><p>La création d'une bibliothèque peut prendre quelques minutes. Vous pouvez arrêter le processus et mettre à jour la bibliothèque plus tard pour terminer la tâche.</p> Some of the comics being added... - Ajout de bande dessinée... + Ajout de certaines bandes dessinées... Updating the library - Mise à jour de la librairie + Mise à jour de la bibliothèque - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>Mise à jour de la librairie. Pour plus de rapidité lors de la mise à jour, veuillez effectuer cette dernière régulièrement.</p><p>Vous pouvez arrêter le processus et poursuivre plus tard.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>La bibliothèque actuelle est en cours de mise à jour. Pour des mises à jour plus rapides, mettez régulièrement vos bibliothèques à jour.</p><p>Vous pouvez arrêter le processus et poursuivre la mise à jour de cette bibliothèque plus tard.</p> @@ -973,11 +1592,11 @@ Scanning the library - Scanner la bibliothèque + Analyse de la bibliothèque - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> <p>La bibliothèque actuelle est en cours d'analyse pour rechercher des informations sur les métadonnées XML héritées.</p><p>Ceci n'est nécessaire qu'une seule fois, et uniquement si la bibliothèque a été créée avec YACReaderLibrary 9.8.2 ou une version antérieure.</p> @@ -986,6 +1605,36 @@ <p>La bibliothèque actuelle est analysée pour rechercher les couvertures manquantes et les informations de BD incomplètes.</p><p>Cette opération peut prendre plusieurs minutes. Vous pouvez l'arrêter et la relancer plus tard.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Impossible de créer le dossier de données de la bibliothèque : %1. Vérifiez les droits d'accès et l'espace disque disponible. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Le dossier de données de la bibliothèque est manquant : %1. Restaurez-le ou recréez la bibliothèque avant de la mettre à jour. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + La base de données de la bibliothèque est manquante : %1. Restaurez une sauvegarde ou recréez la bibliothèque avant de la mettre à jour. + + + + + Unable to start a library database transaction: %1 + Impossible de démarrer une transaction dans la base de données de la bibliothèque : %1 + + + + + Unable to commit the library database: %1 + Impossible de valider la transaction dans la base de données de la bibliothèque : %1 + + LibraryInfoView @@ -1009,82 +1658,170 @@ Nombre de BD lues + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Cette bibliothèque est occupée. Attendez la fin de la maintenance. Si elle semble bloquée, fermez l'application indiquée dans Détails, puis réessayez. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Une opération de maintenance antérieure n'a peut-être pas libéré cette bibliothèque. Avant de rétablir l'accès, assurez-vous qu'aucune opération de YACReaderLibrary ou YACReaderLibraryServer ne l'utilise, y compris sur un autre ordinateur. + + + + Retry + Réessayer + + + + Recover and retry + Rétablir l'accès et réessayer + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Le propriétaire du verrou de la bibliothèque a changé. Relancez l'opération et vérifiez le nouveau propriétaire avant de rétablir l'accès. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Impossible de supprimer le verrou de maintenance : %1. Vérifiez que le fichier n'est pas en lecture seule et que vous pouvez écrire dans le dossier de données de la bibliothèque, puis réessayez. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Un dossier occupe le chemin du fichier de verrouillage de maintenance : %1. Renommez ou déplacez ce dossier avant de réessayer. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Impossible de créer le fichier de verrouillage de la bibliothèque : %1. Vérifiez les droits d'écriture dans le dossier de données de la bibliothèque. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Le dossier de données de la bibliothèque est manquant : %1. Restaurez le dossier ou recréez la bibliothèque avant de la mettre à jour. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + La bibliothèque possède un verrou de maintenance dont le propriétaire n'a pas pu être vérifié (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Une autre opération de maintenance utilise cette bibliothèque (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Impossible de créer le fichier de verrouillage de la bibliothèque : %1. Vérifiez l'espace disque disponible et l'accès au dossier de données de la bibliothèque. + + LibraryManagementCoordinator - + Error opening the library - Erreur lors de l'ouverture de la librairie + Erreur lors de l'ouverture de la bibliothèque - + Error creating the library - Erreur lors de la création de la librairie + Erreur lors de la création de la bibliothèque - + Error updating the library - Erreur lors de la mise à jour de la librairie + Erreur lors de la mise à jour de la bibliothèque + + + + + XML information rescan + Nouvelle analyse des informations XML + + + + Restore recovery + Récupération de la restauration + + + + Library upgrade + Mise à niveau de la bibliothèque + + + + Library maintenance + Maintenance de la bibliothèque + + + + Recover library access + Rétablir l'accès à la bibliothèque LibraryWindow - + The selected folder doesn't contain any library. - Le dossier sélectionné ne contient aucune librairie. + Le dossier sélectionné ne contient aucune bibliothèque. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - Cette librairie a été créée avec une ancienne version de YACReaderLibrary. Mise à jour necessaire. Mettre à jour? + Cette bibliothèque a été créée avec une ancienne version de YACReaderLibrary. Une mise à jour est nécessaire. Mettre à jour maintenant ? - Remove and delete metadata - Supprimer les métadata - - - + Old library - Ancienne librairie + Ancienne bibliothèque - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - Cette librairie a été créée avec une version plus récente de YACReaderLibrary. Télécharger la nouvelle version? + Cette bibliothèque a été créée avec une version plus récente de YACReaderLibrary. Télécharger la nouvelle version maintenant ? - + Moving comics... - Déplacer la bande dessinée... + Déplacement des bandes dessinées... - + Copying comics... - Copier la bande dessinée... + Copie des bandes dessinées... - + Library '%1' is no longer available. Do you want to remove it? - La librarie '%1' n'est plus disponible. Voulez-vous la supprimer? + La bibliothèque « %1 » n'est plus disponible. Voulez-vous la supprimer ? - - Do you want remove - Voulez-vous supprimer + + Do you want to remove the library '%1'? + Voulez-vous supprimer la bibliothèque « %1 » ? - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - La librarie '%1' a été créée avec une ancienne version de YACReaderLibrary. Elle doit être re-créée. Voulez-vous créer la librairie? + La bibliothèque « %1 » a été créée avec une ancienne version de YACReaderLibrary. Elle doit être recréée. Voulez-vous créer la bibliothèque maintenant ? - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. Vous ajoutez trop de bibliothèques. Vous n'avez probablement besoin que d'une bibliothèque dans votre dossier BD de niveau supérieur, vous pouvez parcourir les sous-dossiers en utilisant la section des dossiers dans la barre latérale gauche. @@ -1092,72 +1829,68 @@ Vous n'avez probablement besoin que d'une bibliothèque dans votre dos YACReaderLibrary ne vous empêchera pas de créer plus de bibliothèques, mais vous devriez garder le nombre de bibliothèques bas. - + Library not available - Librairie non disponible + Bibliothèque non disponible - + YACReader Library - Librairie de YACReader + Bibliothèque YACReader - + Update needed Mise à jour requise - + Library name already exists - Le nom de la librairie existe déjà + Le nom de la bibliothèque existe déjà - + There is another library with the name '%1'. - Une autre librairie a le nom '%1'. + Une autre bibliothèque porte le nom « %1 ». - + Download new version - Téléchrger la nouvelle version + Télécharger la nouvelle version - + Delete comics - Supprimer les comics + Supprimer les bandes dessinées - + All the selected comics will be deleted from your disk. Are you sure? - Tous les comics sélectionnés vont être supprimés de votre disque. Êtes-vous sûr? + Toutes les bandes dessinées sélectionnées seront supprimées de votre disque. Êtes-vous sûr ? - + Library not found - Librairie introuvable + Bibliothèque introuvable - - library? - la librairie? - - - + Are you sure? - Êtes-vous sûr? + Êtes-vous sûr ? + Delete folder Supprimer le dossier - + Upgrade failed La mise à niveau a échoué - + There were errors during library upgrade in: Des erreurs se sont produites lors de la mise à niveau de la bibliothèque dans : @@ -1177,7 +1910,7 @@ YACReaderLibrary ne vous empêchera pas de créer plus de bibliothèques, mais v - Please, select a folder first + Please select a folder first Veuillez d'abord sélectionner un dossier @@ -1193,92 +1926,121 @@ YACReaderLibrary ne vous empêchera pas de créer plus de bibliothèques, mais v The selected folder and all its contents will be deleted from your disk. Are you sure? - Le dossier sélectionné et tout son contenu seront supprimés de votre disque. Es-tu sûr? + Le dossier sélectionné et tout son contenu seront supprimés de votre disque. Êtes-vous sûr ? - + Unable to delete Impossible de supprimer - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. Un problème est survenu lors de la tentative de suppression des dossiers sélectionnés. Veuillez vérifier les autorisations d'écriture et vous assurer qu'aucune application n'utilise ces dossiers ni aucun des fichiers qu'ils contiennent. - + + Edit smart list... + Modifier la liste intelligente... + + + + Delete smart list + Supprimer la liste intelligente + + + + + + New folder... + Nouveau dossier... + + + + + + Move to folder... + Déplacer vers un dossier... + + + + Rename folder... + Renommer le dossier... + + + + Match to library comics... + Associer aux BD de la bibliothèque... + + + + Track missing issues... + Suivre les numéros manquants... + + + + Export reading list... + Exporter la liste de lecture... + + + Rename or organize files Renommer ou organiser les fichiers - + Set the type of the selected comics Définir le type des bandes dessinées sélectionnées - + + Save as smart list… + Enregistrer comme liste intelligente… + + + Search filters Filtres de recherche - + Unread Non lus - + In progress En cours - + Highly rated Très bien notés - + Recently added Ajoutés récemment - + Search syntax… Syntaxe de recherche… - - A repair of this library is already running (%1). Wait for it to finish. - Une réparation de cette librairie est déjà en cours (%1). Attendez qu'elle se termine. - - - - The library is locked by a repair that did not finish. - La librairie est verrouillée par une réparation qui ne s'est pas terminée. - - - - The library is locked by a repair started by %1. - La librairie est verrouillée par une réparation démarrée par %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Si vous êtes sûr qu'aucune autre réparation n'est en cours, le verrou peut être supprimé. Supprimer le verrou et continuer ? - - - + Package operation failed Échec de l'opération de paquet - + The covers package operation could not be completed. L'opération sur le paquet de couvertures n'a pas pu être terminée. - + Restore recovery failed Échec de la récupération de la restauration @@ -1307,7 +2069,7 @@ YACReaderLibrary ne vous empêchera pas de créer plus de bibliothèques, mais v A file or folder named '%1' already exists. - Un fichier ou un dossier nommé « %1 » existe déjà. + Un fichier ou un dossier nommé « %1 » existe déjà. @@ -1316,7 +2078,7 @@ YACReaderLibrary ne vous empêchera pas de créer plus de bibliothèques, mais v Folder: %1 Le dossier n'a pas pu être renommé sur le disque. Vérifiez le nom du dossier et les droits d'écriture. -Dossier : %1 +Dossier : %1 @@ -1329,58 +2091,52 @@ Dossier : %1 La base de données de la bibliothèque n'a pas pu être mise à jour et le renommage du dossier sur le disque n'a pas pu être annulé. La bibliothèque doit maintenant être mise à jour manuellement. - + Save covers Enregistrer les couvertures - + You are adding too many libraries. Vous ajoutez trop de bibliothèques. - - + + YACReader library database (*.ydb) Base de données de bibliothèque YACReader (*.ydb) - + The library database backup was created at: %1 - La sauvegarde de la base de données de la bibliothèque a été créée ici : + La sauvegarde de la base de données de la bibliothèque a été créée ici : %1 - + Unable to create the library database backup: %1 - Impossible de créer la sauvegarde de la base de données de la bibliothèque : + Impossible de créer la sauvegarde de la base de données de la bibliothèque : %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? - Fermez YACReaderLibraryServer et toute autre application YACReader utilisant cette bibliothèque avant la restauration. Continuer ? + Fermez YACReaderLibraryServer et toute autre application YACReader utilisant cette bibliothèque avant la restauration. Continuer ? - + Restoring library database... Restauration de la base de données de la bibliothèque... - + The current library database is invalid. Restore the selected backup anyway? - La base de données actuelle de la bibliothèque n'est pas valide. Restaurer quand même la sauvegarde sélectionnée ? - - - - - The library maintenance lock may be stale. Remove it and retry? - Le verrou de maintenance de la bibliothèque est peut-être obsolète. Le supprimer et réessayer ? + La base de données actuelle de la bibliothèque n'est pas valide. Restaurer quand même la sauvegarde sélectionnée ? - + Restart YACReaderLibrary before attempting recovery again. @@ -1389,174 +2145,221 @@ Restart YACReaderLibrary before attempting recovery again. Redémarrez YACReaderLibrary avant de tenter à nouveau la récupération. - + The library database was restored successfully. Update the library now? - La base de données de la bibliothèque a été restaurée. Mettre à jour la bibliothèque maintenant ? + La base de données de la bibliothèque a été restaurée. Mettre à jour la bibliothèque maintenant ? - + Library database damaged Base de données de la bibliothèque endommagée - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. - La base de données de la bibliothèque « %1 » est endommagée. Les mises à jour, la maintenance et les sauvegardes habituelles sont donc indisponibles. YACReader peut tenter de réparer la base de données. Certaines données endommagées peuvent être irrécupérables. Les sauvegardes existantes ne seront pas modifiées. + La base de données de la bibliothèque « %1 » est endommagée. Les mises à jour, la maintenance et les sauvegardes habituelles sont donc indisponibles. YACReader peut tenter de réparer la base de données. Certaines données endommagées peuvent être irrécupérables. Les sauvegardes existantes ne seront pas modifiées. - + Attempt repair Tenter la réparation - + Restore a backup... Restaurer une sauvegarde... - + Repairing library database... Réparation de la base de données... - - + + + Library database repair Réparation de la base de données de la bibliothèque - - Another maintenance operation is currently using this library. Try again after it finishes. - Une autre opération de maintenance utilise actuellement cette bibliothèque. Réessayez lorsqu'elle sera terminée. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Vérifier la base de données de la bibliothèque '%1' et tenter de la réparer si nécessaire ? Les sauvegardes existantes ne seront pas modifiées. + + + + Check and repair + Vérifier et réparer - + The library database is already valid. La base de données de la bibliothèque est déjà valide. - + Library database repaired Base de données de la bibliothèque réparée - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 - La base de données de la bibliothèque a été réparée en reconstruisant ses index. L'original endommagé a été conservé ici : + La base de données de la bibliothèque a été réparée en reconstruisant ses index. L'original endommagé a été conservé ici : %1 - + Library database rebuilt Base de données de la bibliothèque reconstruite - + The library database was rebuilt successfully. The damaged original was preserved at: %1 Update the library now? - La base de données de la bibliothèque a été reconstruite. L'original endommagé a été conservé ici : + La base de données de la bibliothèque a été reconstruite. L'original endommagé a été conservé ici : %1 -Mettre à jour la bibliothèque maintenant ? +Mettre à jour la bibliothèque maintenant ? - + The damaged original was preserved at: %1 -L'original endommagé a été conservé ici : +L'original endommagé a été conservé ici : %1 - + Library database repair failed Échec de la réparation de la base de données - + The library database could not be repaired: %1%2 You can restore a backup from the Library menu or recreate the library. - La base de données de la bibliothèque n'a pas pu être réparée : + La base de données de la bibliothèque n'a pas pu être réparée : %1%2 Vous pouvez restaurer une sauvegarde depuis le menu Bibliothèque ou recréer la bibliothèque. - + Remove and delete metadata and backups Retirer et supprimer les métadonnées et les sauvegardes - + Library info Informations sur la bibliothèque - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. Un problème est survenu lors de la tentative de suppression des bandes dessinées sélectionnées. Veuillez vérifier les autorisations d'écriture dans les fichiers sélectionnés ou le dossier contenant. - + Assign comics numbers Attribuer des numéros de bandes dessinées - - Assign numbers starting in: + + Assign numbers starting at: Attribuez des numéros commençant par : - + Invalid image Image invalide - + The selected file is not a valid image. Le fichier sélectionné n'est pas une image valide. - + + Error saving cover Erreur lors de l'enregistrement de la couverture - + + There was an error saving the cover image. Une erreur s'est produite lors de l'enregistrement de l'image de couverture. - + + + Match to library comic + Associer à une BD de la bibliothèque + + + + This also gives the comic to entries in %n other list(s): %1 + + Cela associe aussi la BD aux entrées de %n autre liste : %1 + Cela associe aussi la BD aux entrées de %n autres listes : %1 + + + + + The comic could not be matched. Nothing was changed. + Impossible d'associer la BD. Rien n'a été modifié. + + + + The selected missing comics will be removed from this folder. Are you sure? + Les BD manquantes sélectionnées seront retirées de ce dossier. Voulez-vous continuer ? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + Les BD sélectionnées seront supprimées de votre disque et les BD manquantes sélectionnées seront retirées de ce dossier. Voulez-vous continuer ? + + + + Remove the comics from Reading? Their reading progress will be reset. + Retirer les BD de « En cours de lecture » ? Leur progression de lecture sera réinitialisée. + + + + The comics will be removed from Favorites. Are you sure? + Les BD seront retirées des favoris. Voulez-vous continuer ? + + + Remove comics - Supprimer les bandes dessinées + Retirer les bandes dessinées - + Comics will only be deleted from the current label/list. Are you sure? - Les bandes dessinées seront uniquement supprimées du label/liste actuelle. Es-tu sûr? + Les bandes dessinées seront uniquement retirées de l'étiquette/liste actuelle. Êtes-vous sûr ? - + Repaired: %1 Failed: %2 Missing files: %3 - Réparés : %1 -Échecs : %2 -Fichiers manquants : %3 + Réparés : %1 +Échecs : %2 +Fichiers manquants : %3 @@ -1564,12 +2367,12 @@ Fichiers manquants : %3 Create a new library - Créer une nouvelle librairie + Créer une nouvelle bibliothèque Open an existing library - Ouvrir une librairie existante + Ouvrir une bibliothèque existante @@ -1591,7 +2394,7 @@ Fichiers manquants : %3 Pack the covers of the selected library - Archiver les couvertures de la librairie sélectionnée + Archiver les couvertures de la bibliothèque sélectionnée @@ -1606,12 +2409,12 @@ Fichiers manquants : %3 Update library - Mettre la librairie à jour + Mettre à jour la bibliothèque Update current library - Mettre à jour la librairie actuelle + Mettre à jour la bibliothèque actuelle @@ -1635,433 +2438,504 @@ Fichiers manquants : %3 + Repair library database... + Réparer la base de données de la bibliothèque... + + + + Check the current library database and attempt to repair any damage + Vérifier la base de données de la bibliothèque actuelle et tenter de réparer les dommages + + + + Recover library access... + Rétablir l'accès à la bibliothèque... + + + + Recover access after an interrupted library maintenance operation + Rétablir l'accès après une opération de maintenance de la bibliothèque interrompue + + + Repair covers and comic info Réparer les couvertures et les informations des BD - + Retry comics with missing covers or incomplete information Réessayer les BD dont la couverture est manquante ou les informations incomplètes - + Rename library - Renommer la librairie + Renommer la bibliothèque - + Rename current library - Renommer la librairie actuelle + Renommer la bibliothèque actuelle - + Remove library - Supprimer la librairie + Retirer la bibliothèque - + Remove current library from your collection - Enlever cette librairie de votre collection + Retirer cette bibliothèque de votre collection - + Rescan library for XML info Réanalyser la bibliothèque pour les informations XML - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Essaie de trouver des informations XML intégrées dans des fichiers de bandes dessinées. Vous ne devez le faire que si la bibliothèque a été créée avec la version 9.8.2 ou des versions antérieures ou si vous utilisez un logiciel tiers pour intégrer des informations XML dans les fichiers. - + Open library folder... Ouvrir le dossier de la bibliothèque... - + Open the root folder of the current library Ouvrir le dossier racine de la bibliothèque actuelle - + Show library info Afficher les informations sur la bibliothèque - + Show information about the current library Afficher des informations sur la bibliothèque actuelle - + Open current comic Ouvrir cette bande dessinée - - Open current comic on YACReader + + Open current comic in YACReader Ouvrir cette bande dessinée dans YACReader - + Save selected covers to... - Exporter la couverture vers... + Exporter les couvertures sélectionnées vers... - + Save covers of the selected comics as JPG files Enregistrer les couvertures des bandes dessinées sélectionnées en tant que fichiers JPG - - + + Set as read Marquer comme lu - + Set comic as read - Marquer cette bande dessinée comme lu + Marquer cette bande dessinée comme lue - - + + Set as unread - Marquer comme non-lu + Marquer comme non lu - + Set comic as unread - Marquer cette bande dessinée comme non-lu + Marquer cette bande dessinée comme non lue - - + + manga - mangas + manga - + Set issue as manga - Définir le problème comme manga + Définir ce numéro comme manga - - + + comic - comique + bande dessinée - + Set issue as normal - Définir le problème comme d'habitude + Définir ce numéro comme normal - + western manga manga occidental - + Set issue as western manga - Définir le problème comme un manga occidental + Définir ce numéro comme manga occidental - - + + web comic - bande dessinée Web + webcomic - + Set issue as web comic - Définir le problème comme bande dessinée Web + Définir ce numéro comme webcomic - - + + yonkoma Yonkoma - + Set issue as yonkoma - Définir le problème comme Yonkoma + Définir ce numéro comme Yonkoma - + Show/Hide marks - Afficher/Cacher les marqueurs + Afficher/masquer les marqueurs - + Show or hide read marks Afficher ou masquer les marques de lecture - + Show/Hide recent indicator Afficher/Masquer l'indicateur récent - + Show or hide recent indicator Afficher ou masquer l'indicateur récent - - + + Fullscreen mode on/off Mode plein écran activé/désactivé - + Help, About YACReader - Aide, à propos de YACReader + Aide, À propos de YACReader - + Add new folder Ajouter un nouveau dossier - + Add new folder to the current library Ajouter un nouveau dossier à la bibliothèque actuelle - + Rename folder Renommer le dossier - + Rename the current folder on disk and in the library Renommer le dossier actuel sur le disque et dans la bibliothèque - + Delete folder Supprimer le dossier - + Delete current folder from disk Supprimer le dossier actuel du disque - + Select root node - Allerà la racine + Sélectionner la racine - + Expand all nodes - Afficher tous les noeuds + Développer tous les nœuds - + Collapse all nodes Réduire tous les nœuds - + Show options dialog - Ouvrir la boite de dialogue + Afficher la boîte de dialogue des options - + Show comics server options dialog - Ouvrir la boite de dialogue du serveur + Afficher la boîte de dialogue des options du serveur de bandes dessinées - - + + Change between comics views - Changement entre les vues de bandes dessinées + Basculer entre les vues de bandes dessinées + + + + Grid view + Vue grille + + + + Flow view + + + + + Info view + - + Open folder... Ouvrir le dossier... - - + + Rename files... Organize files Renommer les fichiers... - - + + Organize into folders... Organiser en dossiers... - + Set as uncompleted Marquer comme incomplet - + Set as completed Marquer comme complet - + Set custom cover Définir une couverture personnalisée - + Delete custom cover Supprimer la couverture personnalisée - + western manga (left to right) manga occidental (de gauche à droite) - + Open containing folder... - Ouvrir le dossier... + Ouvrir le dossier parent... - Reset comic rating - Supprimer la note d'évaluation + + New reading list + Nouvelle liste de lecture - + Select all comics Sélectionner toutes les bandes dessinées - + Edit - Editer + Modifier - + Assign current order to comics Assigner l'ordre actuel aux bandes dessinées - + Update cover - Mise à jour des couvertures + Mettre à jour la couverture - + Delete selected comics - Supprimer la bande dessinée sélectionnée + Supprimer les bandes dessinées sélectionnées - + Delete metadata from selected comics Supprimer les métadonnées des bandes dessinées sélectionnées - + Download tags from Comic Vine Télécharger les informations de Comic Vine - + Focus search line - Ligne de recherche ciblée + Placer le focus sur la recherche - + Focus comics view Focus sur la vue des bandes dessinées - + Edit shortcuts Modifier les raccourcis - + &Quit &Quitter - + Update folder Mettre à jour le dossier - + Update current folder Mettre à jour ce dossier - + Scan legacy XML metadata Analyser les métadonnées XML héritées - - Add new reading list - Ajouter une nouvelle liste de lecture - - - + Add a new reading list to the current library Ajouter une nouvelle liste de lecture à la bibliothèque actuelle - + + New smart list... + Nouvelle liste intelligente... + + + + Add a saved search to the current library + Ajouter une recherche enregistrée à la bibliothèque actuelle + + + + New folder... + Nouveau dossier... + + + + Add a folder for reading lists or smart lists + Ajouter un dossier pour les listes de lecture ou les listes intelligentes + + + + Import reading lists... + Importer des listes de lecture... + + + + Import CBL reading lists into the current library + Importer des listes de lecture CBL dans la bibliothèque actuelle + + + + New + Nouveau + + + + Add or import reading lists + Ajouter ou importer des listes de lecture + + + Remove reading list Supprimer la liste de lecture - + Remove current reading list from the library Supprimer la liste de lecture actuelle de la bibliothèque - + Add new label Ajouter une nouvelle étiquette - + Add a new label to this library Ajouter une nouvelle étiquette à cette bibliothèque - + Rename selected list Renommer la liste sélectionnée - + Rename any selected labels or lists Renommer toutes les étiquettes ou listes sélectionnées - + Add to... Ajouter à... - + Favorites Favoris - + Add selected comics to favorites list - Ajouter la bande dessinée sélectionnée à la liste des favoris + Ajouter les bandes dessinées sélectionnées à la liste des favoris - - + + Reset rating Réinitialiser la note @@ -2071,12 +2945,12 @@ Fichiers manquants : %3 comic - comique + bande dessinée manga - mangas + manga @@ -2086,95 +2960,108 @@ Fichiers manquants : %3 web comic - bande dessinée Web + webcomic - 4koma (top to botom) + 4koma (top to bottom) 4koma (de haut en bas) - - - + + + Set type Définir le type - + Library - Librairie + Bibliothèque - + Folder Dossier - + Comic Bande dessinée - + + Match to library comic... + Associer à une BD de la bibliothèque... + + + + + + + Track missing issues... + Suivre les numéros manquants... + + + Open folder... Ouvrir le dossier... - + Update folder Mettre à jour le dossier - + Rename folder Renommer le dossier - + Rename files... Renommer les fichiers... - + Organize into folders... Organiser en dossiers... - + Rescan library for XML info Réanalyser la bibliothèque pour les informations XML - + Set as uncompleted Marquer comme incomplet - + Set as completed Marquer comme complet - + Set as read Marquer comme lu - - + + Set as unread - Marquer comme non-lu + Marquer comme non lu - + Set custom cover Définir une couverture personnalisée - - + + Delete custom cover Supprimer la couverture personnalisée @@ -2215,32 +3102,78 @@ Fichiers manquants : %3 LocalComicListModel - + file name nom de fichier + + MoveToFolderDialog + + + Move to folder + Déplacer vers un dossier + + + + Top level + Niveau supérieur + + + + Move "%1" to: + Déplacer « %1 » vers : + + + + NewListFolderDialog + + + New folder + Nouveau dossier + + + + Reading lists + Listes de lecture + + + + Smart lists + Listes intelligentes + + + + Folder name: + Nom du dossier : + + + + For: + Pour : + + NoLibrariesWidget create your first library - Créez votre première librairie + Créez votre première bibliothèque You don't have any libraries yet - Vous n'avez pas encore de librairie + Vous n'avez pas encore de bibliothèque - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>Vous pouvez creer une librairie dans n'importe quel dossierr, YACReaderLibrary importera les dossiers et les bandes dessinées contenus dans ce dossier. Si vous avez déjà crer des librairies, vous pouvez les ouvrir.</p><p>N'oubliez pas que vous pouvez utiliser YACReader en tant que stand alone pour lire vos bandes dessinées sur votre ordinateur.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Vous pouvez créer une bibliothèque dans n'importe quel dossier, YACReaderLibrary importera tous les dossiers et bandes dessinées qu'il contient. Si vous avez déjà créé des bibliothèques, vous pouvez les ouvrir.</p><p>N'oubliez pas que vous pouvez utiliser YACReader comme application autonome pour lire vos bandes dessinées sur votre ordinateur.</p> add an existing one - Ajouter une librairie existante + Ajouter une bibliothèque existante @@ -2261,7 +3194,7 @@ Fichiers manquants : %3 Options - Possibilités + Options @@ -2281,17 +3214,17 @@ Fichiers manquants : %3 Tray icon settings (experimental) - Paramètres de l'icône de la barre d'état (expérimental) + Paramètres de l'icône de la zone de notification (expérimental) Close to tray - Près du plateau + Fermer dans la zone de notification - Start into the system tray - Commencez dans la barre d'état système + Start in the system tray + Démarrer dans la zone de notification @@ -2317,7 +3250,7 @@ Fichiers manquants : %3 Consider 'recent' items added or updated since X days ago - Considérez les éléments « récents » ajoutés ou mis à jour depuis X jours + Considérer comme « récents » les éléments ajoutés ou mis à jour depuis X jours @@ -2327,12 +3260,12 @@ Fichiers manquants : %3 Write {comic_file_path} where the path should go in the command - Écrivez {comic_file_path} où le chemin doit aller dans la commande + Écrivez {comic_file_path} à l'endroit de la commande où le chemin doit figurer Clear - Clair + Effacer @@ -2342,7 +3275,7 @@ Fichiers manquants : %3 Try to detect changes automatically - Essayez de détecter automatiquement les changements + Essayer de détecter automatiquement les changements @@ -2352,12 +3285,12 @@ Fichiers manquants : %3 Interval: - Intervalle: + Intervalle : 30 minutes - 30 min + 30 minutes @@ -2397,18 +3330,18 @@ Fichiers manquants : %3 Time: - Temps: + Heure : - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. - AVERTISSEMENT! Lors des mises à jour de la bibliothèque, les écritures dans la base de données sont désactivées ! + AVERTISSEMENT ! Lors des mises à jour de la bibliothèque, les écritures dans la base de données sont désactivées ! Ne planifiez pas de mises à jour pendant que vous utilisez activement l'application. Lors des mises à jour automatiques, l'application bloquera certaines actions jusqu'à ce que la mise à jour soit terminée. Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de chargement à côté du titre Bibliothèques. @@ -2445,7 +3378,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha - Restore defautls + Restore defaults Restaurer les valeurs par défaut @@ -2456,7 +3389,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Display continue reading banner - Afficher la bannière de lecture continue + Afficher la bannière « Continuer la lecture » @@ -2513,76 +3446,177 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha OrganizeFiles - + + + Unknown Series + Série inconnue + + + + series + série + + + + Unknown Publisher + Éditeur inconnu + + + + publisher + éditeur + + + + title + titre + + + + Unknown Comic + BD inconnue + + + + Series #Number - Title + Série #Numéro - Titre + + + + Series #Number + Série #Numéro + + + + Series #Number (of Count) + Série #Numéro (sur Total) + + + + Number - Title + Numéro - Titre + + + + Series #Number (Year) + Série #Numéro (Année) + + + + Publisher / Series / Series #Number - Title + Éditeur / Série / Série #Numéro - Titre + + + + Publisher / Imprint / Series / Series #Number - Title + Éditeur / Collection éditoriale / Série / Série #Numéro - Titre + + + + Series / Series #Number + Série / Série #Numéro + + + + Publisher / Series / Series #Number (Year) + Éditeur / Série / Série #Numéro (Année) + + + + Series / original file name + Série / nom du fichier d'origine + + + Renamed, %1 is already in use Renommé, %1 est déjà utilisé - + Missing metadata: %1 - Métadonnées manquantes : %1 + Métadonnées manquantes : %1 %1 could not be created %1 n'a pas pu être créé + + + %1 (copy also failed: %2) + %1 (la copie a aussi échoué : %2) + + + + the copy did not have the same size as the original + la copie n'avait pas la même taille que l'original + + + + the original could not be deleted after it was copied + l'original n'a pas pu être supprimé après la copie + + + + The destination folder could not be created. + Impossible de créer le dossier de destination. + + + + %1 (the file was left at %2) + %1 (le fichier a été laissé dans %2) + OrganizeFilesCoordinator - - + + Organize files Organiser les fichiers - + This folder does not contain any comics. Ce dossier ne contient aucune bande dessinée. - - This library is busy: %1 - Cette bibliothèque est occupée : %1 - - - + the library database could not be opened la base de données de la bibliothèque n'a pas pu être ouverte - + the library database could not be locked for writing la base de données de la bibliothèque n'a pas pu être verrouillée en écriture - + a folder entry could not be restored une entrée de dossier n'a pas pu être restaurée - + a comic entry could not be updated une entrée de bande dessinée n'a pas pu être mise à jour - + the library database could not be saved: %1 - la base de données de la bibliothèque n'a pas pu être enregistrée : %1 + la base de données de la bibliothèque n'a pas pu être enregistrée : %1 - + the record of the last organize run could not be read l'enregistrement de la dernière organisation n'a pas pu être lu - + the folder %1 could not be created le dossier %1 n'a pas pu être créé - + %n file(s) could not be moved back %n fichier n'a pas pu être remis en place @@ -2592,10 +3626,6 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha OrganizeFilesDialog - - Format: - Format : - Organize files @@ -2615,12 +3645,12 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha &Filename format: - &Format du nom de fichier : + &Format du nom de fichier : &Path format: - Format du &chemin : + Format du &chemin : @@ -2727,19 +3757,11 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Cancel Annuler - - Copy the list - Copier la liste - Undo Revenir en arrière - - Close - Fermer - Copy failure details @@ -2773,7 +3795,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Preset name: - Nom du préréglage: + Nom du préréglage : @@ -2783,7 +3805,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha This format cannot be used: %1 - Ce format ne peut pas être utilisé : %1 + Ce format ne peut pas être utilisé : %1 @@ -2941,7 +3963,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Moved back from - Redéplacé depuis + Remis en place depuis @@ -2994,12 +4016,12 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Undo failed: %1 - Échec de l’annulation : %1 + Échec de l'annulation : %1 Failed: %1 - Échec : %1 + Échec : %1 @@ -3009,7 +4031,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha The record this run could be undone from could not be written, so the run did not start: %1 - L'enregistrement permettant d'annuler cette opération n'a pas pu être écrit, l'opération n'a donc pas démarré : %1 + L'enregistrement permettant d'annuler cette opération n'a pas pu être écrit, l'opération n'a donc pas démarré : %1 @@ -3030,7 +4052,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha The record of this run stopped early, so the run stopped with it: %1 - L'enregistrement de cette opération s'est arrêté prématurément, l'opération s'est donc arrêtée avec lui : %1 + L'enregistrement de cette opération s'est arrêté prématurément, l'opération s'est donc arrêtée avec lui : %1 @@ -3043,7 +4065,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha The library database could not be updated: %1 - La base de données de la bibliothèque n'a pas pu être mise à jour : %1 + La base de données de la bibliothèque n'a pas pu être mise à jour : %1 @@ -3086,7 +4108,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha The undo did not finish: %1 - Le retour en arrière ne s'est pas terminé : %1 + Le retour en arrière ne s'est pas terminé : %1 @@ -3136,7 +4158,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Write a colon and some zeros to pad the issue number. This keeps the issues in order in a file browser. - Écrivez deux-points et quelques zéros pour compléter le numéro. Les numéros restent ainsi dans l'ordre dans un gestionnaire de fichiers. + Écrivez le signe deux-points puis quelques zéros pour compléter le numéro. Les numéros restent ainsi dans l'ordre dans un gestionnaire de fichiers. @@ -3158,276 +4180,388 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha PropertiesDialog - - + + of: - sur: + sur : - + Day: - Jour: + Jour : - + Plot Intrigue - + Size: - Taille: + Taille : - + Year: - Année: + Année : - + Inker(s): - Encreur(s): + Encreur(s) : - + Publishing Publication - + Publisher: - Editeur: + Éditeur : - + General info Infos générales - + Color/BW: - Couleur/Noir et blanc: + Couleur/Noir et blanc : - + Edit selected comics information - Editer les informations du comic sélectionné + Modifier les informations des bandes dessinées sélectionnées - + Penciller(s): - Dessinateur(s): + Dessinateur(s) : - + Colorist(s): - Coloriste(s): + Coloriste(s) : - + Genre: - Genre : + Genre : - + Notes Remarques - + Load previous page as cover Charger la page précédente comme couverture - + Load next page as cover Charger la page suivante comme couverture - + Reset cover to the default image Réinitialiser la couverture à l'image par défaut - + Load custom cover image Charger une image de couverture personnalisée - + Series: - Série: + Série : + + + + e.g. 1-5, 8, 0.5, Annual 1 + p. ex. 1-5, 8, 0.5, Annual 1 - + + Issue number: - Numéro: + Numéro : - + alt. number: - alt. nombre: + n° alt. : - + Alternate series: Série alternative : - + Series Group: Groupe de séries : - + Editor(s): - Editeur(s) : + Responsable(s) éditorial(aux) : - + Imprint: - Imprimer: + Label éditorial : - + Month: - Mois: + Mois : - + Type: - Taper: + Type : - + Language (ISO): Langue (ISO) : - + Teams: Équipes : - + Locations: - Emplacements : + Lieux : - + Main character or team: Personnage principal ou équipe : - + Review: - Revoir: + Critique : - + Notes: Remarques : - + + + Track missing issues in "%1" + Suivre les numéros manquants dans « %1 » + + + + Issues: + Numéros : + + + + Track + Suivre + + + + At most %1 issues can be tracked at once. + Vous pouvez suivre au maximum %1 numéros à la fois. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Vérifiez les numéros : une plage doit être croissante et aucune partie entre les virgules ne peut être vide. + + + + No new issues to track. + Aucun nouveau numéro à suivre. + + + + %n issue(s) will be tracked: %1. + + %n numéro sera suivi : %1. + %n numéros seront suivis : %1. + + + + + That is a lot of issues; check the numbers. + Cela fait beaucoup de numéros ; vérifiez la numérotation. + + + + %n issue(s) already in the folder. + + %n numéro est déjà dans le dossier. + %n numéros sont déjà dans le dossier. + + + + + %n repeated issue(s). + + %n numéro répété. + %n numéros répétés. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Numéros non suivis + + + + The missing issues could not be tracked. + Impossible de suivre les numéros manquants. + + + + Comic skipped + BD ignorée + + + + A comic was removed from the library or now has a different file, so it is skipped. + Une BD a été retirée de la bibliothèque ou est désormais associée à un autre fichier ; elle est donc ignorée. + + + + + Changes not saved + Modifications non enregistrées + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Une BD que vous modifiez a été retirée de la bibliothèque ou est désormais associée à un autre fichier. Vos modifications n'ont pas été enregistrées. + + + + The changes could not be saved. + Impossible d'enregistrer les modifications. + + + Invalid cover Couverture invalide - + The image is invalid. L'image n'est pas valide. - + Synopsis: - Synopsis : + Synopsis : - + Title: - Titre: + Titre : - + Arc number: - Arc numéro: + Numéro de l'arc : - + Tags: - Balises : + Étiquettes : - + Not found Introuvable - + Characters: - Personnages: + Personnages : - + Authors Auteurs - + Age rating: - Limite d'âge: + Classification d'âge : - + Story arc: - Arc narratif: + Arc narratif : - + Writer(s): - Scénariste(s): + Scénariste(s) : - + Comic not found. You should update your library. - Comic introuvable. Vous devriez mettre à jour votre librairie. + Bande dessinée introuvable. Vous devriez mettre à jour votre bibliothèque. - + Edit comic information - Editer les informations du comic + Modifier les informations de la bande dessinée - + Cover page Couverture - + Cover Artist(s): - Artiste(s) de couverture: + Artiste(s) de couverture : - + Volume: - Tome : + Tome : - + Format: - Format : + Format : - + Letterer(s): - Lettreur(s): + Lettreur(s) : - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - Lien Comic Vine : <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> vue </a> + Lien Comic Vine : <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> voir </a> @@ -3435,22 +4569,22 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha 7z lib not found - lib 7z introuvable + Bibliothèque 7z introuvable unable to load 7z lib from ./utils - impossible de charger 7z depuis ./utils + impossible de charger la bibliothèque 7z depuis ./utils - + Select custom cover Sélectionnez une couverture personnalisée - + Images (%1) - Illustrations (%1) + Images (%1) @@ -3481,38 +4615,173 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha ReadingListManagementCoordinator - + Add new reading lists Ajouter de nouvelles listes de lecture - - + + List name: Nom de la liste : - + + Import reading lists + Importer des listes de lecture + + + + Reading lists (*.cbl *.json);;All files (*) + Listes de lecture (*.cbl *.json);;Tous les fichiers (*) + + + + New smart list + Nouvelle liste intelligente + + + + + The smart list could not be saved. + Impossible d'enregistrer la liste intelligente. + + + + Edit smart list + Modifier la liste intelligente + + + + Delete folder + Supprimer le dossier + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Supprimer le dossier « %1 » et tout son contenu ? Ses dossiers et listes seront supprimés, mais vos fichiers de BD seront conservés. + + + Delete list/label Supprimer la liste/l'étiquette - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - L'élément sélectionné sera supprimé, vos bandes dessinées ou dossiers ne seront pas supprimés de votre disque. Êtes-vous sûr? + L'élément sélectionné sera supprimé, vos bandes dessinées ou dossiers ne seront PAS supprimés de votre disque. Êtes-vous sûr ? - + Rename list name Renommer le nom de la liste + + + Rename folder + Renommer le dossier + + + + + Folder name: + Nom du dossier : + + + + + + New folder + Nouveau dossier + + + + + The folder could not be created. + Impossible de créer le dossier. + + + + Reading list + Liste de lecture + + + + CBL reading list (*.cbl) + Liste de lecture CBL (*.cbl) + + + + JSON reading list (*.json) + Liste de lecture JSON (*.json) + + + + Web page (*.html) + Page web (*.html) + + + + + Export reading list + Exporter la liste de lecture + + + + Exporting reading list... + Exportation de la liste de lecture... + + + + The file could not be written. + Impossible d'écrire le fichier. + + + + Move to folder + Déplacer vers un dossier + + + + "%1" could not be moved. + Impossible de déplacer « %1 ». + + + + ReadingListPage + + + %n comic(s) + + %n BD + %n BD + + + + + %n missing + + %n introuvable + %n introuvables + + + + + Missing + Manquant + + + + Exported from YACReader on %1 + Exporté depuis YACReader le %1 + RenameLibraryDialog Rename current library - Renommer la librairie actuelle + Renommer la bibliothèque actuelle @@ -3526,26 +4795,26 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha - New Library Name : - Nouveau nom de librairie: + New Library Name: + Nouveau nom de la bibliothèque : ScraperResultsPaginator - Number of volumes found : %1 - Nombre de volumes trouvés : %1 + Number of volumes found: %1 + Nombre de volumes trouvés : %1 page %1 of %2 - page %1 de %2 + page %1 sur %2 - Number of %1 found : %2 + Number of %1 found: %2 Nombre de %1 trouvés : %2 @@ -3644,7 +4913,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Editor credit - Crédit de l'éditeur + Crédit du responsable éditorial @@ -3833,7 +5102,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Series: - Série: + Série : @@ -3908,6 +5177,11 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Just start typing. Plain text search across all metadata. Commencez simplement à saisir du texte. Le texte simple est recherché dans toutes les métadonnées. + + + %1 -> search for `%1` in any database field + %1 -> rechercher `%1` dans tous les champs de la base de donn?es + 1. Search everywhere @@ -3926,7 +5200,7 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Use a field name followed by : or = - Utilisez un nom de champ suivi de : ou = + Utilisez un nom de champ suivi de : ou = @@ -4003,7 +5277,9 @@ Pour arrêter une mise à jour automatique, appuyez sur l'indicateur de cha Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Conseils : les espaces agissent comme AND et les recherches ne tiennent pas compte de la casse. Utilisez des guillemets pour inclure des espaces dans une valeur. + Conseils : +Les espaces agissent comme AND et les recherches ne tiennent pas compte de la casse. +Utilisez des guillemets pour inclure des espaces dans une valeur. @@ -4146,7 +5422,7 @@ Use quotes to include spaces in a value. Series: - Série: + Série : @@ -4158,7 +5434,7 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. + Please select the right comic info. Veuillez sélectionner les bonnes informations sur la bande dessinée. @@ -4169,12 +5445,12 @@ Use quotes to include spaces in a value. loading cover - couvercle de chargement + chargement de la couverture loading description - description du chargement + chargement de la description @@ -4186,18 +5462,18 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. Veuillez sélectionner la bonne série pour votre bande dessinée. Filter: - Filtre: + Filtre : volumes - tomes + volumes @@ -4207,12 +5483,12 @@ Use quotes to include spaces in a value. loading cover - couvercle de chargement + chargement de la couverture loading description - description du chargement + chargement de la description @@ -4225,54 +5501,59 @@ Use quotes to include spaces in a value. Unknown - Inconnu + Inconnu %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 inconnues) %1 series - + %1 séries - + %1 comics selected %n comic(s) selected - %1 bande(s) dessinnée(s) sélectionnée(s) + %1 bande(s) dessinée(s) sélectionnée(s) - + Read - Lu + Lu - + In progress - En cours + En cours - + Unread - Non lus + Non lus - + Total pages - + Nombre total de pages - + Total size - + Taille totale - + + Missing + Manquant + + + Series - Série + Série @@ -4289,8 +5570,8 @@ Use quotes to include spaces in a value. - You are trying to get information for various comics at once, are they part of the same series? - Vous essayez d’obtenir des informations sur plusieurs bandes dessinées à la fois, font-elles partie de la même série ? + You are trying to get information for various comics at once. Are they part of the same series? + Vous essayez d'obtenir des informations sur plusieurs bandes dessinées à la fois, font-elles partie de la même série ? @@ -4309,7 +5590,7 @@ Use quotes to include spaces in a value. Devices on this network can reach your library at the address below. - Les appareils de ce réseau peuvent accéder à votre bibliothèque à l’adresse ci-dessous. + Les appareils de ce réseau peuvent accéder à votre bibliothèque à l'adresse ci-dessous. @@ -4319,7 +5600,7 @@ Use quotes to include spaces in a value. Port - Port r?seau + Port @@ -4334,7 +5615,7 @@ Use quotes to include spaces in a value. Open web UI - Ouvrir l’interface web + Ouvrir l'interface web @@ -4346,60 +5627,76 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader est disponible pour iOS et Android. Découvrez-le pour <a href='https://ios.yacreader.com'>iOS</a> ou <a href='https://android.yacreader.com'>Android</a>. - - enable the server - Autoriser le serveur - Set port set port Définir le port + + + SmartListDialog + + + New smart list + Nouvelle liste intelligente + - Server connectivity information - Informations sur la connectivité du serveur + + Edit smart list + Modifier la liste intelligente - Scan it! - Scannez-le ! + + Search filters and syntax + Filtres et syntaxe de recherche - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader est disponible pour les appareils iOS et Android.<br/>Découvrez-le pour <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> ou <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Name: + Nom : - Choose an IP address - Choisissez une adresse IP + + Search: + Recherche : SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. Veuillez trier la liste des bandes dessinées sur la gauche jusqu'à ce qu'elle corresponde aux informations des bandes dessinées. - + sort comics to match comic information trier les bandes dessinées pour qu'elles correspondent aux informations sur les bandes dessinées - + issues - problèmes + numéros - + remove selected comics supprimer les bandes dessinées sélectionnées - + restore all removed comics restaurer toutes les bandes dessinées supprimées + + + Add the %n other issue(s) of this volume as missing comics + + Ajouter l'autre numéro de ce volume comme BD manquante (%n) + Ajouter les %n autres numéros de ce volume comme BD manquantes + + ThemeEditorDialog @@ -4421,7 +5718,7 @@ Use quotes to include spaces in a value. i - je + i @@ -4436,7 +5733,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Maintenez enfoncé pour faire clignoter la valeur sélectionnée dans l'interface utilisateur (magenta / basculé / 0↔10). Les versions restaurent l'original. + Maintenez enfoncé pour faire clignoter la valeur sélectionnée dans l'interface (magenta / basculé / 0↔10). Le relâchement restaure l'original. @@ -4446,7 +5743,7 @@ Use quotes to include spaces in a value. Light - Lumière + Clair @@ -4456,7 +5753,7 @@ Use quotes to include spaces in a value. ID: - IDENTIFIANT: + ID : @@ -4466,7 +5763,7 @@ Use quotes to include spaces in a value. Variant: - Variante: + Variante : @@ -4486,7 +5783,7 @@ Use quotes to include spaces in a value. Save and apply - Enregistrer et postuler + Enregistrer et appliquer @@ -4516,7 +5813,7 @@ Use quotes to include spaces in a value. true - vrai + true @@ -4524,7 +5821,7 @@ Use quotes to include spaces in a value. false - FAUX + false @@ -4595,7 +5892,7 @@ Use quotes to include spaces in a value. Expected a JSON object. - Attendu un objet JSON. + Un objet JSON était attendu. @@ -4603,7 +5900,7 @@ Use quotes to include spaces in a value. SEARCH - RECHERCHE + RECHERCHER @@ -4611,7 +5908,7 @@ Use quotes to include spaces in a value. Update library - Mettre la librairie à jour + Mettre à jour la bibliothèque @@ -4620,10 +5917,18 @@ Use quotes to include spaces in a value. - Updating.... + Updating... Mise à jour... + + ViewSelector + + + View + + + VolumeComicsModel @@ -4647,7 +5952,7 @@ Use quotes to include spaces in a value. issues - problèmes + numéros @@ -4655,7 +5960,7 @@ Use quotes to include spaces in a value. Presets: - Réglages: + Réglages : @@ -4690,7 +5995,7 @@ Use quotes to include spaces in a value. Custom: - Personnalisation: + Personnalisé : @@ -4715,17 +6020,17 @@ Use quotes to include spaces in a value. Zoom - Agrandissement + Zoom Y offset - Axe Y + Décalage Y Z offset - Axe Z + Décalage Z @@ -4750,7 +6055,7 @@ Use quotes to include spaces in a value. Low Performance - Basse performance + Faible performance @@ -4759,13 +6064,13 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Utiliser VSync (améliore la qualité de l'image en plein écran, diminue la performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Utiliser VSync (améliore la qualité d'image en plein écran, diminue les performances) Performance: - Performance : + Performance : @@ -4783,7 +6088,7 @@ Use quotes to include spaces in a value. YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary continuera à fonctionner dans la barre d'état système. Pour terminer le programme, choisissez <b>Quitter</b> dans le menu contextuel de l'icône de la barre d'état système. + YACReaderLibrary continuera à fonctionner dans la zone de notification. Pour terminer le programme, choisissez <b>Quitter</b> dans le menu contextuel de l'icône de la zone de notification. @@ -4799,6 +6104,44 @@ Use quotes to include spaces in a value. Versions précédentes + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + La base de données de la bibliothèque est manquante. Restaurez une sauvegarde ou recréez la bibliothèque avant de relancer l'analyse des informations XML. + + + + Unable to load the archive backend: %1 + Impossible de charger le moteur d'archives : %1 + + + + Unable to start an XML rescan transaction. + Impossible de démarrer une transaction de nouvelle analyse XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Le dossier sélectionné n'est plus dans cette bibliothèque. Rechargez la bibliothèque avant de relancer l'analyse. + + + + Unable to read comics for the XML rescan. + Impossible de lire les BD pour la nouvelle analyse XML. + + + + Unable to commit XML metadata changes. + Impossible de valider les modifications des métadonnées XML. + + + + Unable to update XML metadata for %1. + Impossible de mettre à jour les métadonnées XML de %1. + + YACReaderFieldEdit @@ -4808,7 +6151,7 @@ Use quotes to include spaces in a value. - + Click to overwrite Cliquer pour modifier @@ -4821,10 +6164,12 @@ Use quotes to include spaces in a value. Restaurer les paramètres par défaut + + - - - + + + Click to overwrite Cliquer pour modifier @@ -4834,7 +6179,7 @@ Use quotes to include spaces in a value. Save - Sauvegarder + Enregistrer @@ -4849,7 +6194,7 @@ Use quotes to include spaces in a value. Customize the keyboard shortcuts used by the application. - Personnalisez les raccourcis clavier utilisés par l’application. + Personnalisez les raccourcis clavier utilisés par l'application. diff --git a/YACReaderLibrary/yacreaderlibrary_it.ts b/YACReaderLibrary/yacreaderlibrary_it.ts index 4abd13304..e18af302b 100644 --- a/YACReaderLibrary/yacreaderlibrary_it.ts +++ b/YACReaderLibrary/yacreaderlibrary_it.ts @@ -11,6 +11,66 @@ AddLabelDialog + + + red + Rosso + + + + orange + Arancione + + + + yellow + Giallo + + + + green + Verde + + + + cyan + Ciano + + + + blue + Blu + + + + violet + Violetto + + + + purple + Porpora + + + + pink + Rosa + + + + white + Bianco + + + + light + Chiaro + + + + dark + Scuro + Choose a color: @@ -24,7 +84,7 @@ cancel - Cancella + Annulla @@ -42,22 +102,22 @@ Add an existing library - Aggiungi ad una libreria esistente + Aggiungi una libreria esistente Cancel - Cancella + Annulla - Comics folder : + Comics folder: Cartella fumetti: - Library name : - Nome della biblioteca: + Library name: + Nome libreria: @@ -70,17 +130,17 @@ Cancel - Cancella + Annulla - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Prima di conneterti a "Comic Vine" devi avere la tua chiave API. Per favore recuperane una da <a href="http://www.comicvine.com/api/">QUI</a + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Prima di connetterti a Comic Vine, devi avere la tua chiave API. Per favore, ottienine una gratuita <a href="http://www.comicvine.com/api/">qui</a> Paste here your Comic Vine API key - Incolla qui la tua chiave API di "Comic Vine" + Incolla qui la tua chiave API di Comic Vine @@ -98,22 +158,22 @@ Light - Luminoso + Chiaro Dark - Buio + Scuro Custom - Costume + Personalizzato Remove - Rimuovere + Rimuovi @@ -123,17 +183,17 @@ Light: - Leggero: + Chiaro: Dark: - Buio: + Scuro: Custom: - Personalizza: + Personalizzato: @@ -148,7 +208,7 @@ Theme editor - Redattore del tema + Editor del tema @@ -163,7 +223,7 @@ The current theme JSON could not be loaded. - Impossibile caricare il tema corrente JSON. + Impossibile caricare il JSON del tema corrente. @@ -200,103 +260,625 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Nascondi Comic Flow + + Find comic › + Trova fumetto › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Sto copiando i fumetti... + + Series + Serie - Moving comics... - Sto muovendo i fumetti... + + Number + Numero + + + + Volume + Volume + + + + Year + Anno + + + + Format + Formato + + + + + ComicVine ID + ID Comic Vine + + + + series and number + serie e numero + + + + series without the volume, and number + serie senza volume e numero + + + + letters and digits of the series, and number + lettere e cifre della serie e numero + + + + series with its year, and number + serie con anno e numero + + + + volume %1 + volume %1 + + + + Comic in your library + Fumetto nella libreria + + + + Missing in your library + Mancante nella libreria + + + + Changed by you. + Modificato da te. + + + + The entry has this comic. + La voce è associata a questo fumetto. + + + + Matched by %1. + Corrispondenza per %1. + + + + Your library already has this comic as missing. Matched by %1. + Questo fumetto è già segnato come mancante nella libreria. Corrispondenza per %1. + + + + Possible comic %1 + Possibile fumetto %1 + + + + + Changed by you: it is imported as missing. + Modificato da te: verrà importato come mancante. + + + + The comics have the same ComicVine ID. + I fumetti hanno lo stesso ID Comic Vine. + + + + The comic has the same series and number, but its volume or year is different. + Il fumetto ha la stessa serie e lo stesso numero, ma volume o anno diversi. + + + + The comics have the same series and number. + I fumetti hanno la stessa serie e lo stesso numero. + + + + Values that are different from the file are in amber. + I valori diversi da quelli del file sono in ambra. + + + + Closest series in your library + Serie più simili nella libreria + + + + Library + Libreria + + + + Has %1 + Contiene %1 + + + + No comic found. Tried: %1. + Nessun fumetto trovato. Tentativi: %1. + + + + In the file + Nel file + + + + Entry %1 of %2 + Voce %1 di %2 + + + + Use this comic + Usa questo fumetto + + + + Use this missing comic + Usa questo fumetto mancante + + + + CblFindComicPage + + + ‹ All entries + ‹ Tutte le voci + + + + Previous problem + Problema precedente + + + + Next problem + Problema successivo + + + + Folders + Cartelle + + + + Search the whole library + Cerca in tutta la libreria + + + + File + File + + + + Series + Serie + + + + No. + N. + + + + Volume + Volume + + + + Year + Anno + + + + Go to the next problem after I choose + Passa al problema successivo dopo la scelta + + + + Use this comic + Usa questo fumetto + + + + Cancel + Annulla + + + + Search in this folder + Cerca in questa cartella + + + + Entry %1 of %2 + Voce %1 di %2 + + + + Missing: no comic found. Browse or search the library. + Mancante: nessun fumetto trovato. Esplora o cerca nella libreria. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Ambiguo: %n fumetto possibile. Esplora o cerca nella libreria. + Ambiguo: %n fumetti possibili. Esplora o cerca nella libreria. + + + + + Browse or search the library to choose another comic. + Esplora o cerca nella libreria per scegliere un altro fumetto. + + + + Problem %1 of %2 + Problema %1 di %2 + + + + %n problem(s) + + %n problema + %n problemi + + + + + + + Library + Libreria + + + + Search results + Risultati della ricerca + + + + CblImportDialog + + + Match to library comics — %1 + Associa ai fumetti della libreria — %1 + + + + Apply + Applica + + + + Close + Chiudi + + + + Skip + Salta + + + + Import + Importa + + + + &Name: + &Nome: + + + + A reading list with this name already exists. Import creates another one. + Esiste già una lista di lettura con questo nome. L'importazione ne crea un'altra. + + + + Filter entries + Filtra voci + + + + Matching the books of %1 with your library... + Associazione dei libri di %1 alla libreria... + + + + Matching the entries of %1 with your library... + Associazione delle voci di %1 alla libreria... + + + + + Match to library comics + Associa ai fumetti della libreria + + + + This also gives comics to entries in %n other list(s): %1 + + Questo associa fumetti anche alle voci di %n altra lista: %1 + Questo associa fumetti anche alle voci di %n altre liste: %1 + + + + + The changes could not be applied. Nothing was changed. + Impossibile applicare le modifiche. Nulla è stato modificato. + + + + The file cannot be opened. + Impossibile aprire il file. + + + + The file is damaged (line %1, column %2). + Il file è danneggiato (riga %1, colonna %2). + + + + The file is not a reading list. + Il file non è una lista di lettura. + + + + The library cannot be read. + Impossibile leggere la libreria. + + + + Import reading lists + Importa liste di lettura + + + + The reading list could not be imported. Nothing was written. + Impossibile importare la lista di lettura. Nulla è stato scritto. + + + + Import reading lists — %1 (%2 of %3) + Importa liste di lettura — %1 (%2 di %3) + + + + Import reading lists — %1 + Importa liste di lettura — %1 + + + + All (%1) + Tutte (%1) + + + + Matched (%1) + Associate (%1) + + + + Ambiguous (%1) + Ambigue (%1) + + + + Missing (%1) + Mancanti (%1) + + + + Changed by you (%1) + Modificate da te (%1) + + + + %1 of %n have a comic + + %1 di %n ha un fumetto + %1 di %n hanno un fumetto + + + + + Find comic... + Trova fumetto... + + + + Use the original match + Usa la corrispondenza originale + + + + Make missing + Segna come mancante + + + + Import as missing + Importa come mancante + + + + CblImportModel + + + Matched + Associato + + + + Ambiguous + Ambiguo + + + + Missing + Mancante + + + + Changed + Modificato + + + + # + # + + + + In the file + Nel file + + + + Comic in your library + Fumetto nella libreria + + + + Status + Stato + + + + Already missing in your library + Già mancante nella libreria + + + + %n possible comic(s) + + %n fumetto possibile + %n fumetti possibili + + + + + Not in library + Non nella libreria + + + + CblMatchComicDialog + + + Match to library comic + Associa a un fumetto della libreria + + + + Loading the library... + Caricamento della libreria... + + + + The library cannot be read. + Impossibile leggere la libreria. + + + + ClassicComicsView + + + Hide comic flow + Nascondi Comic Flow ComicInfoView - + Characters Personaggi - + Main character or team Personaggio principale o squadra - + Teams Squadre - + Locations - Posizioni + Luoghi - + Authors Autori - + writer sceneggiatore - + penciller disegnatore - + inker inchiostratore - + colorist colorista - + letterer letterista - + cover artist disegnatore di copertina - + editor - editor + curatore - + imprint marchio editoriale - + Publisher Editore - + color colore - + b/w b/n @@ -304,28 +886,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader non trovato - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader non trovato. YACReader deve essere installato nella stessa cartella di YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader non trovato. Potrebbe esserci un problema con l'installazione di YACReader. - + Error Errore - + Error opening comic with third party reader. Errore nell'apertura del fumetto con un lettore di terze parti. @@ -333,67 +915,67 @@ ComicModel - + no No - + yes - Si + Sì - + Read - Leggi + Letto - + Series Serie - + Volume - Tomo + Volume - + Story Arc Arco narrativo - + Size Dimensione - + Pages Pagine - + Title Titolo - + Current Page Pagina corrente - + File Name Nome file - + Publication Date Data di pubblicazione - + Rating Valutazione @@ -401,68 +983,112 @@ ComicVineDialog - + back Indietro - + next Prossimo - + skip Salta - + close Chiudi - - - Retrieving tags for : %1 - Ricezione tag per: %1 + + + Retrieving tags for: %1 + Recupero dei tag per: %1 - + + Creating missing comic %1 of %2 + Creazione del fumetto mancante %1 di %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine non ha risposto per %n fumetto. + Comic Vine non ha risposto per %n fumetti. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n fumetto mancante ha ricevuto solo i dati di base del volume. + %n fumetti mancanti hanno ricevuto solo i dati di base del volume. + + + + + %n missing comic(s) have no cover. + + %n fumetto mancante non ha copertina. + %n fumetti mancanti non hanno copertina. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine consente circa 200 richieste all'ora; riprova più tardi per i fumetti non aggiornati. + + + + Try again later for the comics that were not updated. + Riprova più tardi per i fumetti non aggiornati. + + + + Comic Vine + Comic Vine + + + Looking for comic... Sto cercando il fumetto... - + search Cerca - - - + + + Looking for volume... - Sto cercando il fumetto... + Sto cercando il volume... - - + + comic %1 of %2 - %3 Fumetto %1 di %2 - %3 - + %1 comics selected - Fumetto %1 selezionato + %1 fumetti selezionati - - Error connecting to ComicVine - Errore durante la connessione a ComicVine + + Error connecting to Comic Vine + Errore durante la connessione a Comic Vine - + Retrieving volume info... - Sto ricevendo le informazioni per l'abum... + Recupero delle informazioni sul volume... @@ -483,7 +1109,7 @@ Cancel - Cancella + Annulla @@ -492,8 +1118,8 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - Creare una Libreria può aver bisogno di alcuni minuti. Puoi fermare il processo ed aggiornare la libreria più tardi. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + La creazione di una libreria può richiedere alcuni minuti. Puoi fermare il processo e aggiornare la libreria più tardi per completare l'attività. @@ -502,12 +1128,12 @@ - Comics folder : + Comics folder: Cartella fumetti: - Library Name : + Library Name: Nome libreria: @@ -519,11 +1145,19 @@ DBHelper - + The folder entry could not be found in the library database. La voce della cartella non è stata trovata nel database della libreria. + + DelayedProgressDialog + + + Cancel + Annulla + + EditShortcutsDialog @@ -534,7 +1168,7 @@ Restore defaults - Resetta al Default + Ripristina i valori predefiniti @@ -543,13 +1177,13 @@ - The shortcut "%1" is already assigned to other function - La scorciatoia "%1" è già assegnata ad un' altra funzione + The shortcut "%1" is already assigned to another function + La scorciatoia "%1" è già assegnata a un'altra funzione - To change a shortcut, double click in the key combination and type the new keys. - Per cambiare una sorciatoia fai doppio click sulla combinazione tasti e digita la nuova combinazione. + To change a shortcut, double-click the key combination and type the new keys. + Per cambiare una scorciatoia, fai doppio clic sulla combinazione di tasti e digita la nuova combinazione. @@ -581,6 +1215,14 @@ Per ora questa etichetta non contiene fumetti + + EmptyListFolderWidget + + + This folder is empty + Questa cartella è vuota + + EmptyReadingListWidget @@ -594,12 +1236,12 @@ No favorites - Nessun Favorito + Nessun preferito You are not reading anything yet, come on!! - Non stai ancora leggendo nulla, Forza!! + Non stai ancora leggendo nulla, forza!! @@ -611,7 +1253,7 @@ ExportComicsInfoDialog - Output file : + Output file: File di Output: @@ -622,7 +1264,7 @@ Cancel - Cancella + Annulla @@ -650,7 +1292,7 @@ Cancel - Cancella + Annulla @@ -664,7 +1306,7 @@ - Output folder : + Output folder: Cartella di Output: @@ -686,34 +1328,27 @@ FileComic - - + + Format not supported Formato non supportato - + 7z not found 7z non trovato - + Unknown error opening the file Errore sconosciuto all'apertura del file - + CRC error on page (%1): some of the pages will not be displayed correctly Errore CRC alla pagina (%1): alcune pagine potrebbero non essere visualizzate correttamente - - FolderContentView - - Continue Reading... - Continua a leggere... - - FolderInfoView @@ -739,7 +1374,7 @@ Read - Leggi + Letto @@ -788,53 +1423,37 @@ GridComicsView - + Show info Mostra informazioni - Library - Libreria - - - Folder - Cartella - - - Favorites - Favoriti - - - Recently added - Aggiunti di recente - - - + Manga Manga - + Western manga Manga occidentale - + Web comic Fumetto web - + Yonkoma Yonkoma - + Comic Fumetto - + Unknown Sconosciuto @@ -867,7 +1486,7 @@ Cancel - Cancella + Annulla @@ -876,8 +1495,8 @@ - Info database location : - Informazioni posizione database: + Info database location: + Posizione del database delle informazioni: @@ -894,13 +1513,13 @@ ImportLibraryDialog - Destination folder : + Destination folder: Cartella di destinazione: Cancel - Cancella + Annulla @@ -909,17 +1528,17 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) Libreria di copertine compresse (*.clc) - Package location : - Posizione PAcchetto: + Package location: + Posizione pacchetto: - Library Name : + Library Name: Nome libreria: @@ -942,8 +1561,8 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YacReader sta creando una nuova libreria.</p><p>La creazione di una libreria può durare diversi minuti. Puoi fermare l'attività ed aggiornare la libreria più tardi, completando l'attività</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary sta creando una nuova libreria.</p><p>La creazione di una libreria può richiedere diversi minuti. Puoi fermare il processo e aggiornare la libreria più tardi per completare l'attività.</p> @@ -953,17 +1572,17 @@ Updating the library - Sto aggiornando la Libreria + Aggiornamento della libreria - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>Quest alibreria si sta aggiornando. Per aggiornamenti più veloci aggiorna la tua libreria di frequente.</p><p>Puoi fermare il processo ed aggiornare la libreria più tardi.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>La libreria corrente è in fase di aggiornamento. Per aggiornamenti più veloci, aggiorna di frequente le tue librerie.</p><p>Puoi fermare il processo e continuare ad aggiornare questa libreria più tardi.</p> Upgrading the library - Aggiornamento della biblioteca + Aggiornamento della libreria @@ -977,7 +1596,7 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> <p>Scansione della libreria corrente per informazioni sui metadati XML legacy.</p><p>Questa operazione è necessaria solo una volta e solo se la libreria è stata creata con YACReaderLibrary 9.8.2 o versioni precedenti.</p> @@ -986,12 +1605,42 @@ <p>La libreria corrente viene controllata per individuare copertine mancanti e informazioni incomplete sui fumetti.</p><p>L'operazione può richiedere diversi minuti. Puoi interromperla ed eseguirla di nuovo in seguito.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Impossibile creare la cartella dati della libreria: %1. Controlla l'accesso alla cartella e lo spazio disponibile sul disco. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + La cartella dati della libreria è mancante: %1. Ripristinala o ricrea la libreria prima di aggiornarla. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Il database della libreria è mancante: %1. Ripristina un backup o ricrea la libreria prima di aggiornarla. + + + + + Unable to start a library database transaction: %1 + Impossibile avviare una transazione nel database della libreria: %1 + + + + + Unable to commit the library database: %1 + Impossibile confermare la transazione nel database della libreria: %1 + + LibraryInfoView Library info - Informazioni sulla biblioteca + Informazioni sulla libreria @@ -1009,35 +1658,127 @@ Numero di fumetti letti + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Questa libreria è occupata. Attendi il termine della manutenzione. Se sembra bloccata, chiudi l'applicazione indicata in Dettagli e riprova. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Una precedente operazione di manutenzione potrebbe non aver rilasciato questa libreria. Prima di recuperare l'accesso, assicurati che nessuna operazione di YACReaderLibrary o YACReaderLibraryServer la stia usando, anche su un altro computer. + + + + Retry + Riprova + + + + Recover and retry + Recupera e riprova + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Il proprietario del blocco della libreria è cambiato. Riprova l'operazione e verifica il nuovo proprietario prima di recuperare l'accesso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Impossibile rimuovere il blocco di manutenzione: %1. Assicurati che il file non sia di sola lettura e di poter scrivere nella cartella dati della libreria, quindi riprova. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Una cartella occupa il percorso del file di blocco di manutenzione: %1. Rinominala o spostala prima di riprovare. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Impossibile creare il file di blocco della libreria: %1. Controlla i permessi di scrittura della cartella dati della libreria. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + La cartella dati della libreria è mancante: %1. Ripristina la cartella o ricrea la libreria prima di aggiornarla. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + La libreria ha un blocco di manutenzione il cui proprietario non è stato verificato (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Un'altra operazione di manutenzione sta usando questa libreria (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Impossibile creare il file di blocco della libreria: %1. Controlla lo spazio disponibile sul disco e l'accesso alla cartella dati della libreria. + + LibraryManagementCoordinator - + Error opening the library Errore nell'apertura della libreria - + Error creating the library - Errore creando la libreria + Errore durante la creazione della libreria - + Error updating the library - Errore aggiornando la libreria + Errore durante l'aggiornamento della libreria + + + + + XML information rescan + Nuova scansione delle informazioni XML + + + + Restore recovery + Recupero del ripristino + + + + Library upgrade + Aggiornamento della libreria + + + + Library maintenance + Manutenzione della libreria + + + + Recover library access + Recupera l'accesso alla libreria LibraryWindow - + The selected folder doesn't contain any library. - La cartella selezionata non contiene nessuna Libreria. + La cartella selezionata non contiene nessuna libreria. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - Questa libreria è stata creata con una versione precedente di YACREaderLibrary. Deve essere aggiornata. Aggiorno ora? + Questa libreria è stata creata con una versione precedente di YACReaderLibrary. Deve essere aggiornata. Aggiornare ora? @@ -1047,20 +1788,16 @@ The selected folder and all its contents will be deleted from your disk. Are you sure? - La cartella seleziona e tutto il suo contenuto verranno cancellati dal tuo disco. Sei sicuro? + La cartella selezionata e tutto il suo contenuto verranno eliminati dal disco. Sei sicuro? - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - C'è stato un problema cancellando le cartelle selezionate. Per favore controlla i tuoi permessi di scrittura e sii sicuro che non ci siano altre applicazioni che usano le stesse cartelle. - - - Remove and delete metadata - Rimuovi e cancella i Metadati + Si è verificato un problema durante l'eliminazione delle cartelle selezionate. Controlla i permessi di scrittura e assicurati che nessuna applicazione stia usando queste cartelle o i file in esse contenuti. - + Old library Vecchia libreria @@ -1070,34 +1807,34 @@ C'è stato un errore nell'accesso al percorso della cartella - + Comics will only be deleted from the current label/list. Are you sure? - I fumetti verranno cancellati dall'etichetta/lista corrente. Sei sicuro? + I fumetti verranno rimossi solo dall'etichetta/lista corrente. Sei sicuro? - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - Questa libreria è stata creata con una verisone più recente di YACReaderLibrary. Scarico la versione aggiornata ora? + Questa libreria è stata creata con una versione più recente di YACReaderLibrary. Scaricare ora la nuova versione? - + Moving comics... - Sto muovendo i fumetti... + Sto spostando i fumetti... - + Copying comics... Sto copiando i fumetti... - + Library '%1' is no longer available. Do you want to remove it? - La libreria '%1' non è più disponibile, la vuoi cancellare? + La libreria '%1' non è più disponibile. Vuoi rimuoverla? - - Do you want remove - Vuoi rimuovere + + Do you want to remove the library '%1'? + Vuoi rimuovere la libreria '%1'? @@ -1105,35 +1842,35 @@ Errore nel percorso - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - La libreria '%1' è stata creata con una versione precedente di YACREaderLibrary. Deve essere ricreata. Lo vuoi fare ora? + La libreria '%1' è stata creata con una versione precedente di YACReaderLibrary. Deve essere ricreata. Vuoi creare la libreria ora? - + Save covers - Salva Copertine + Salva copertine - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. - Stai aggiungendo troppe Libreire +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. + Stai aggiungendo troppe librerie. -Hai probabilemnte bisogno di una sola Libreria al livello superiore, puoi poi navigare qualsiasi sotto cartella usando la selezione delle cartella a sinistra +Probabilmente ti serve una sola libreria nella cartella dei fumetti di livello superiore: puoi esplorare qualsiasi sottocartella usando la sezione delle cartelle nella barra laterale sinistra. -YACReader non ti fermerà dal creare altre librerie ma è meglio se terrai il numero di librerie basso. +YACReaderLibrary non ti impedirà di creare altre librerie, ma è meglio tenerne il numero basso. - + Library info - Informazioni sulla biblioteca + Informazioni sulla libreria - + Assign comics numbers Assegna un numero ai fumetti @@ -1141,90 +1878,122 @@ YACReader non ti fermerà dal creare altre librerie ma è meglio se terrai il nu - Please, select a folder first + Please select a folder first Per cortesia prima seleziona una cartella - + Library not available Libreria non disponibile - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. C'è un problema nel cancellare i fumetti selezionati. Per favore controlla i tuoi permessi di scrittura sui file o sulla cartella. - + YACReader Library Libreria YACReader - + You are adding too many libraries. - Stai aggiungendto troppe librerie. + Stai aggiungendo troppe librerie. - + Update needed - Devi aggiornarmi + Aggiornamento necessario - + Library name already exists Esiste già una libreria con lo stesso nome - + There is another library with the name '%1'. Esiste già una libreria con il nome '%1'. + Delete folder - Cancella Cartella + Cancella cartella - - Assign numbers starting in: + + Assign numbers starting at: Assegna numeri partendo da: - + Download new version Scarica la nuova versione - + Remove and delete metadata and backups Rimuovi ed elimina metadati e backup - + Invalid image Immagine non valida - + The selected file is not a valid image. Il file selezionato non è un'immagine valida. - + + Error saving cover Errore durante il salvataggio della copertina - + + There was an error saving the cover image. Si è verificato un errore durante il salvataggio dell'immagine di copertina. - + + + Match to library comic + Associa a un fumetto della libreria + + + + This also gives the comic to entries in %n other list(s): %1 + + Questo associa il fumetto anche alle voci di %n altra lista: %1 + Questo associa il fumetto anche alle voci di %n altre liste: %1 + + + + + The comic could not be matched. Nothing was changed. + Impossibile associare il fumetto. Nulla è stato modificato. + + + + The selected missing comics will be removed from this folder. Are you sure? + I fumetti mancanti selezionati verranno rimossi da questa cartella. Confermi? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + I fumetti selezionati verranno eliminati dal disco e i fumetti mancanti selezionati verranno rimossi da questa cartella. Confermi? + + + Delete comics Cancella i fumetti @@ -1236,98 +2005,137 @@ YACReader non ti fermerà dal creare altre librerie ma è meglio se terrai il nu Nessuna cartella selezionata - + All the selected comics will be deleted from your disk. Are you sure? Tutti i fumetti selezionati saranno cancellati dal tuo disco. Sei sicuro? - + + Remove the comics from Reading? Their reading progress will be reset. + Rimuovere i fumetti da In lettura? I progressi di lettura verranno azzerati. + + + + The comics will be removed from Favorites. Are you sure? + I fumetti verranno rimossi dai Preferiti. Confermi? + + + Remove comics Rimuovi i fumetti - + Library not found Libreria non trovata - + Unable to delete - Non posso cancellare + Impossibile cancellare + + + + Edit smart list... + Modifica lista intelligente... + + + + Delete smart list + Elimina lista intelligente - + + + + New folder... + Nuova cartella... + + + + + + Move to folder... + Sposta nella cartella... + + + + Rename folder... + Rinomina cartella... + + + + Match to library comics... + Associa ai fumetti della libreria... + + + + Track missing issues... + Tieni traccia dei numeri mancanti... + + + + Export reading list... + Esporta lista di lettura... + + + Rename or organize files Rinomina o organizza i file - + Set the type of the selected comics Imposta il tipo dei fumetti selezionati - + + Save as smart list… + Salva come lista intelligente… + + + Search filters Filtri di ricerca - + Unread Non letti - + In progress In corso - + Highly rated Con valutazione alta - + Recently added Aggiunti di recente - + Search syntax… Sintassi di ricerca… - - A repair of this library is already running (%1). Wait for it to finish. - È già in corso una riparazione di questa libreria (%1). Attendere il completamento. - - - - The library is locked by a repair that did not finish. - La libreria è bloccata da una riparazione non completata. - - - - The library is locked by a repair started by %1. - La libreria è bloccata da una riparazione avviata da %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Se sei sicuro che non sia in corso nessun'altra riparazione, il blocco può essere rimosso. Rimuovere il blocco e continuare? - - - + Package operation failed - Operazione di pacchetto non riuscita + Operazione sul pacchetto non riuscita - + The covers package operation could not be completed. Non è stato possibile completare l'operazione con il pacchetto di copertine. - + Restore recovery failed Recupero del ripristino non riuscito @@ -1378,48 +2186,42 @@ Cartella: %1 Non è stato possibile aggiornare il database della libreria né annullare la rinomina della cartella sul disco. Ora la libreria deve essere aggiornata manualmente. - - + + YACReader library database (*.ydb) Database della libreria YACReader (*.ydb) - + The library database backup was created at: %1 Il backup del database della libreria è stato creato in: %1 - + Unable to create the library database backup: %1 Impossibile creare il backup del database della libreria: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? Chiudi YACReaderLibraryServer e qualsiasi altra applicazione YACReader che usa questa libreria prima del ripristino. Continuare? - + Restoring library database... Ripristino del database della libreria... - + The current library database is invalid. Restore the selected backup anyway? Il database attuale della libreria non è valido. Ripristinare comunque il backup selezionato? - - - The library maintenance lock may be stale. Remove it and retry? - Il blocco di manutenzione della libreria potrebbe essere obsoleto. Rimuoverlo e riprovare? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1428,71 +2230,77 @@ Restart YACReaderLibrary before attempting recovery again. Riavvia YACReaderLibrary prima di tentare nuovamente il recupero. - + The library database was restored successfully. Update the library now? Il database della libreria è stato ripristinato correttamente. Aggiornare la libreria ora? - + Library database damaged Database della libreria danneggiato - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. Il database della libreria '%1' è danneggiato, quindi gli aggiornamenti, la manutenzione e i backup normali non sono disponibili. YACReader può tentare di riparare il database. Alcuni dati danneggiati potrebbero non essere recuperabili. I backup esistenti non verranno modificati. - + Attempt repair Tenta la riparazione - + Restore a backup... Ripristina un backup... - + Repairing library database... Riparazione del database della libreria... - - + + + Library database repair Riparazione del database della libreria - - Another maintenance operation is currently using this library. Try again after it finishes. - Un'altra operazione di manutenzione sta usando questa libreria. Riprova al termine. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Controllare il database della libreria '%1' e tentare di ripararlo se necessario? I backup esistenti non verranno modificati. + + + + Check and repair + Controlla e ripara - + The library database is already valid. Il database della libreria è già valido. - + Library database repaired Database della libreria riparato - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 Il database della libreria è stato riparato ricostruendone gli indici. L'originale danneggiato è stato conservato in: %1 - + Library database rebuilt Database della libreria ricostruito - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1503,7 +2311,7 @@ Update the library now? Aggiornare la libreria ora? - + The damaged original was preserved at: @@ -1514,12 +2322,12 @@ L'originale danneggiato è stato conservato in: %1 - + Library database repair failed Riparazione del database della libreria non riuscita - + The library database could not be repaired: %1%2 @@ -1530,27 +2338,22 @@ You can restore a backup from the Library menu or recreate the library. Puoi ripristinare un backup dal menu Libreria o ricreare la libreria. - - library? - Libreria? - - - + Are you sure? Sei sicuro? - + Upgrade failed Aggiornamento non riuscito - + There were errors during library upgrade in: Si sono verificati errori durante l'aggiornamento della libreria in: - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1586,32 +2389,32 @@ File mancanti: %3 Pack covers - Compatta Copertine + Crea pacchetto copertine Pack the covers of the selected library - Compatta le copertine della libreria selezionata + Crea il pacchetto delle copertine della libreria selezionata Unpack covers - Scompatta le Copertine + Decomprimi le copertine Unpack a catalog - Scompatta un catalogo + Decomprimi un catalogo Update library - Aggiorna Libreria + Aggiorna libreria Update current library - Aggiorna la Libreria corrente + Aggiorna la libreria corrente @@ -1635,433 +2438,504 @@ File mancanti: %3 + Repair library database... + Ripara database della libreria... + + + + Check the current library database and attempt to repair any damage + Controlla il database della libreria corrente e tenta di riparare eventuali danni + + + + Recover library access... + Recupera l'accesso alla libreria... + + + + Recover access after an interrupted library maintenance operation + Recupera l'accesso dopo una manutenzione della libreria interrotta + + + Repair covers and comic info Ripara copertine e informazioni dei fumetti - + Retry comics with missing covers or incomplete information Riprova i fumetti con copertine mancanti o informazioni incomplete - + Rename library Rinomina la libreria - + Rename current library Rinomina la libreria corrente - + Remove library Rimuovi la libreria - + Remove current library from your collection Rimuovi la libreria corrente dalla tua collezione - + Rescan library for XML info - Eseguire nuovamente la scansione della libreria per informazioni XML + Riesamina la libreria per informazioni XML - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Cerca di trovare informazioni XML incorporate nei file dei fumetti. Devi farlo solo se la libreria è stata creata con la versione 9.8.2 o precedente o se utilizzi software di terze parti per incorporare informazioni XML nei file. - + Open library folder... Apri la cartella della libreria... - + Open the root folder of the current library Apri la cartella principale della libreria corrente - + Show library info - Mostra informazioni sulla biblioteca + Mostra informazioni sulla libreria - + Show information about the current library Mostra informazioni sulla libreria corrente - + Open current comic Apri il fumetto corrente - - Open current comic on YACReader + + Open current comic in YACReader Apri il fumetto corrente con YACReader - + Save selected covers to... Salva le copertine selezionate in... - + Save covers of the selected comics as JPG files Salva le copertine dei fumetti selezionati come file JPG - - + + Set as read - Setta come letto + Segna come letto - + Set comic as read - Setta il fumetto come letto + Segna il fumetto come letto - - + + Set as unread - Setta come non letto + Segna come non letto - + Set comic as unread - Setta il fumetto come non letto + Segna il fumetto come non letto - - + + manga Manga - + Set issue as manga - Imposta il problema come manga + Imposta l'albo come manga - - + + comic - comico + fumetto - + Set issue as normal - Imposta il problema come normale + Imposta l'albo come normale - + western manga - manga occidentali + manga occidentale - + Set issue as western manga - Imposta il problema come manga occidentale + Imposta l'albo come manga occidentale - - + + web comic fumetto web - + Set issue as web comic - Imposta il problema come fumetto web + Imposta l'albo come fumetto web - - + + yonkoma Yonkoma - + Set issue as yonkoma - Imposta il problema come Yonkoma + Imposta l'albo come Yonkoma - + Show/Hide marks - Mostra/Nascondi + Mostra/Nascondi segni - + Show or hide read marks Mostra o nascondi lo stato di lettura - + Show/Hide recent indicator Mostra/Nascondi l'indicatore recente - + Show or hide recent indicator Mostra o nascondi l'indicatore recente - - + + Fullscreen mode on/off - Modalità a schermo interno on/off + Modalità schermo intero attiva/disattiva - + Help, About YACReader - Aiuto, Crediti YACReader + Aiuto, informazioni su YACReader - + Add new folder Aggiungi una nuova cartella - + Add new folder to the current library Aggiungi una nuova cartella alla libreria corrente - + Rename folder Rinomina cartella - + Rename the current folder on disk and in the library Rinomina la cartella corrente sul disco e nella libreria - + Delete folder - Cancella Cartella + Cancella cartella - + Delete current folder from disk Cancella la cartella corrente dal disco - + Select root node Seleziona il nodo principale - + Expand all nodes Espandi tutti i nodi - + Collapse all nodes Compatta tutti i nodi - + Show options dialog Mostra le opzioni - + Show comics server options dialog Mostra le opzioni per il server dei fumetti - - + + Change between comics views Cambia tra i modi di visualizzazione dei fumetti - + + Grid view + Vista a griglia + + + + Flow view + + + + + Info view + + + + Open folder... - Apri Cartella... + Apri cartella... - - + + Rename files... Organize files Rinomina i file... - - + + Organize into folders... Organizza in cartelle... - + Set as uncompleted Segna come non completo - + Set as completed Segna come completo - + Set custom cover Imposta la copertina personalizzata - + Delete custom cover Elimina la copertina personalizzata - + western manga (left to right) manga occidentale (da sinistra a destra) - + Open containing folder... - Apri la cartella dei contenuti... + Apri la cartella contenitore... - Reset comic rating - Resetta la valutazione dei fumetti + + New reading list + Nuova lista di lettura - + Select all comics Seleziona tutti i fumetti - + Edit - Edita + Modifica - + Assign current order to comics Assegna l'ordinamento corrente ai fumetti - + Update cover Aggiorna copertina - + Delete selected comics Cancella i fumetti selezionati - + Delete metadata from selected comics Elimina i metadati dai fumetti selezionati - + Download tags from Comic Vine - Scarica i Tag da Comic Vine + Scarica i tag da Comic Vine - + Focus search line - Mettere a fuoco la linea di ricerca + Attiva il campo di ricerca - + Focus comics view - Focus sulla visualizzazione dei fumetti + Attiva la visualizzazione dei fumetti - + Edit shortcuts - Edita scorciatoie + Modifica scorciatoie - + &Quit &Esci - + Update folder - Aggiorna Cartella + Aggiorna cartella - + Update current folder Aggiorna la cartella corrente - + Scan legacy XML metadata - Scansione dei metadati XML legacy + Scansiona i metadati XML legacy - - Add new reading list - Aggiorna la lista di lettura - - - + Add a new reading list to the current library Aggiungi una lista di lettura alla libreria corrente - + + New smart list... + Nuova lista intelligente... + + + + Add a saved search to the current library + Aggiungi una ricerca salvata alla libreria corrente + + + + New folder... + Nuova cartella... + + + + Add a folder for reading lists or smart lists + Aggiungi una cartella per liste di lettura o liste intelligenti + + + + Import reading lists... + Importa liste di lettura... + + + + Import CBL reading lists into the current library + Importa liste di lettura CBL nella libreria corrente + + + + New + Nuovo + + + + Add or import reading lists + Aggiungi o importa liste di lettura + + + Remove reading list Rimuovi la lista di lettura - + Remove current reading list from the library Rimuovi la lista di lettura dalla libreria - + Add new label Aggiungi una nuova etichetta - + Add a new label to this library Aggiungi una nuova etichetta a questa libreria - + Rename selected list Rinomina la lista selezionata - + Rename any selected labels or lists Rinomina qualsiasi etichetta o lista selezionata - + Add to... Aggiungi a... - + Favorites Favoriti - + Add selected comics to favorites list Aggiungi i fumetti selezionati alla lista dei favoriti - - + + Reset rating Reimposta valutazione @@ -2071,7 +2945,7 @@ File mancanti: %3 comic - comico + fumetto @@ -2090,91 +2964,104 @@ File mancanti: %3 - 4koma (top to botom) + 4koma (top to bottom) 4koma (dall'alto verso il basso) - - - + + + Set type Imposta il tipo - + Library Libreria - + Folder Cartella - + Comic Fumetto - + + Match to library comic... + Associa a un fumetto della libreria... + + + + + + + Track missing issues... + Tieni traccia dei numeri mancanti... + + + Open folder... - Apri Cartella... + Apri cartella... - + Update folder - Aggiorna Cartella + Aggiorna cartella - + Rename folder Rinomina cartella - + Rename files... Rinomina i file... - + Organize into folders... Organizza in cartelle... - + Rescan library for XML info - Eseguire nuovamente la scansione della libreria per informazioni XML + Riesamina la libreria per informazioni XML - + Set as uncompleted Segna come non completo - + Set as completed Segna come completo - + Set as read - Setta come letto + Segna come letto - - + + Set as unread - Setta come non letto + Segna come non letto - + Set custom cover Imposta la copertina personalizzata - - + + Delete custom cover Elimina la copertina personalizzata @@ -2192,32 +3079,78 @@ File mancanti: %3 %1 fumetti - - Last day - Ultimo giorno + + Last day + Ultimo giorno + + + + Last %1 days + Ultimi %1 giorni + + + + 1 sublist + 1 sottolista + + + + %1 sublists + %1 sottoliste + + + + LocalComicListModel + + + file name + Nome file + + + + MoveToFolderDialog + + + Move to folder + Sposta nella cartella + + + + Top level + Livello superiore + + + + Move "%1" to: + Sposta "%1" in: + + + + NewListFolderDialog + + + New folder + Nuova cartella - - Last %1 days - Ultimi %1 giorni + + Reading lists + Liste di lettura - - 1 sublist - 1 sottolista + + Smart lists + Liste intelligenti - - %1 sublists - %1 sottoliste + + Folder name: + Nome della cartella: - - - LocalComicListModel - - file name - Nome file + + For: + Per: @@ -2234,8 +3167,8 @@ File mancanti: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>Puoi creare una libreria in qualsiasi cartella, YACReader importerà tutti i fumetti e struttura da questa certella. Se hai creato una qualsiasia libreria nel passato la puoi aprire. </p><p>Non dimenticare che puoi usare YACReader come applicazione stand alone per leggere i fumetti sul tuo PC.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Puoi creare una libreria in qualsiasi cartella. YACReaderLibrary importerà tutti i fumetti e le cartelle presenti in questa cartella. Se in passato hai già creato delle librerie, puoi aprirle.</p><p>Non dimenticare che puoi usare YACReader come applicazione autonoma per leggere i fumetti sul tuo PC.</p> @@ -2255,8 +3188,8 @@ File mancanti: %3 OptionsDialog - Restore defautls - Resetta al Default + Restore defaults + Ripristina i valori predefiniti @@ -2266,7 +3199,7 @@ File mancanti: %3 Blur level - Livello di sfumatura + Livello di sfocatura @@ -2281,12 +3214,12 @@ File mancanti: %3 Comic Vine API key - API di ComicVine + Chiave API di Comic Vine Edit Comic Vine API key - Edita l'API di ComicVine + Modifica la chiave API di Comic Vine @@ -2301,7 +3234,7 @@ File mancanti: %3 Use selected comic cover as background - Usa la cover del fumetto selezionato come sfondo + Usa la copertina del fumetto selezionato come sfondo @@ -2317,7 +3250,7 @@ File mancanti: %3 Grid view - Vista a Griglia + Vista a griglia @@ -2342,17 +3275,17 @@ File mancanti: %3 Tray icon settings (experimental) - Impostazioni dell'icona nella barra delle applicazioni (sperimentale) + Impostazioni dell'icona nell'area di notifica (sperimentale) Close to tray - Vicino al vassoio + Chiudi nell'area di notifica - Start into the system tray - Inizia nella barra delle applicazioni + Start in the system tray + Avvia nell'area di notifica @@ -2448,18 +3381,18 @@ File mancanti: %3 Time: - Tempo: + Ora: - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. - AVVERTIMENTO! Durante gli aggiornamenti della libreria le scritture sul database sono disabilitate! + ATTENZIONE! Durante gli aggiornamenti della libreria le scritture sul database sono disabilitate! Non pianificare gli aggiornamenti mentre potresti utilizzare l'app attivamente. Durante gli aggiornamenti automatici l'app bloccherà alcune azioni fino al termine dell'aggiornamento. Per interrompere un aggiornamento automatico, tocca l'indicatore di caricamento accanto al titolo Librerie. @@ -2477,7 +3410,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Display continue reading banner - Visualizza il banner continua a leggere + Visualizza il banner "Continua a leggere" @@ -2507,18 +3440,99 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Restart is needed - Riavvio Necessario + Riavvio necessario OrganizeFiles - + + + Unknown Series + Serie sconosciuta + + + + series + serie + + + + Unknown Publisher + Editore sconosciuto + + + + publisher + editore + + + + title + titolo + + + + Unknown Comic + Fumetto sconosciuto + + + + Series #Number - Title + Serie #Numero - Titolo + + + + Series #Number + Serie #Numero + + + + Series #Number (of Count) + Serie #Numero (di Totale) + + + + Number - Title + Numero - Titolo + + + + Series #Number (Year) + Serie #Numero (Anno) + + + + Publisher / Series / Series #Number - Title + Editore / Serie / Serie #Numero - Titolo + + + + Publisher / Imprint / Series / Series #Number - Title + Editore / Etichetta editoriale / Serie / Serie #Numero - Titolo + + + + Series / Series #Number + Serie / Serie #Numero + + + + Publisher / Series / Series #Number (Year) + Editore / Serie / Serie #Numero (Anno) + + + + Series / original file name + Serie / nome file originale + + + Renamed, %1 is already in use Rinominato, %1 è già in uso - + Missing metadata: %1 Metadati mancanti: %1 @@ -2527,62 +3541,82 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam %1 could not be created Non è stato possibile creare %1 + + + %1 (copy also failed: %2) + %1 (anche la copia è fallita: %2) + + + + the copy did not have the same size as the original + la copia non aveva le stesse dimensioni dell'originale + + + + the original could not be deleted after it was copied + impossibile eliminare l'originale dopo la copia + + + + The destination folder could not be created. + Impossibile creare la cartella di destinazione. + + + + %1 (the file was left at %2) + %1 (il file è rimasto in %2) + OrganizeFilesCoordinator - - + + Organize files Organizza i file - + This folder does not contain any comics. Questa cartella non contiene fumetti. - - This library is busy: %1 - Questa libreria è occupata: %1 - - - + the library database could not be opened non è stato possibile aprire il database della libreria - + the library database could not be locked for writing non è stato possibile bloccare il database della libreria per la scrittura - + a folder entry could not be restored non è stato possibile ripristinare una voce di cartella - + a comic entry could not be updated non è stato possibile aggiornare una voce di fumetto - + the library database could not be saved: %1 non è stato possibile salvare il database della libreria: %1 - + the record of the last organize run could not be read non è stato possibile leggere il registro dell'ultima organizzazione - + the folder %1 could not be created non è stato possibile creare la cartella %1 - + %n file(s) could not be moved back non è stato possibile riportare indietro %n file @@ -2592,10 +3626,6 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam OrganizeFilesDialog - - Format: - Formato: - Organize files @@ -2727,19 +3757,11 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Cancel Annulla - - Copy the list - Copia l'elenco - Undo Ripristina - - Close - Chiudi - Copy failure details @@ -3158,274 +4180,386 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam PropertiesDialog - + Day: Giorno: - + Plot Trama - + Size: - Dimesioni: + Dimensioni: - + Year: Anno: - + Inker(s): Inchiostratore(i): - + Publishing Pubblicazione - + Publisher: Editore: - + General info Informazioni generali - + Color/BW: Colore o B/N: - + Edit selected comics information - Edita le informazioni del fumetto selezionato + Modifica le informazioni dei fumetti selezionati - + Penciller(s): - Mine: + Disegnatore/i: - + Colorist(s): Colorista(i): - + Genre: Genere: - + Notes Note - + Load previous page as cover Carica la pagina precedente come copertina - + Load next page as cover Carica la pagina successiva come copertina - + Reset cover to the default image Ripristina la copertina sull'immagine predefinita - + Load custom cover image Carica l'immagine di copertina personalizzata - + Series: Serie: - + + e.g. 1-5, 8, 0.5, Annual 1 + es. 1-5, 8, 0.5, Annual 1 + + + + Issue number: - Numeri emessi: + Numero dell'albo: - + alt. number: - alt. numero: + n. alt.: - + Alternate series: - Serie alternative: + Serie alternativa: - + Series Group: Gruppo di serie: - + Editor(s): - Redattore(i): + Curatore/i: - + Imprint: - Impronta: + Marchio editoriale: - + Month: Mese: - + Type: Tipo: - + Language (ISO): Lingua (ISO): - + Teams: Squadre: - + Locations: - Posizioni: + Luoghi: - + Main character or team: Personaggio principale o squadra: - + Review: - Revisione: + Recensione: - + Notes: Note: - + + + Track missing issues in "%1" + Tieni traccia dei numeri mancanti in "%1" + + + + Issues: + Numeri: + + + + Track + Tieni traccia + + + + At most %1 issues can be tracked at once. + Puoi tenere traccia di un massimo di %1 numeri alla volta. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Controlla i numeri: gli intervalli devono essere crescenti e le parti tra le virgole non possono essere vuote. + + + + No new issues to track. + Nessun nuovo numero da tenere sotto controllo. + + + + %n issue(s) will be tracked: %1. + + Verrà tenuta traccia di %n numero: %1. + Verrà tenuta traccia di %n numeri: %1. + + + + + That is a lot of issues; check the numbers. + Sono molti numeri; controlla la numerazione. + + + + %n issue(s) already in the folder. + + %n numero è già nella cartella. + %n numeri sono già nella cartella. + + + + + %n repeated issue(s). + + %n numero ripetuto. + %n numeri ripetuti. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Numeri non monitorati + + + + The missing issues could not be tracked. + Impossibile tenere traccia dei numeri mancanti. + + + + Comic skipped + Fumetto saltato + + + + A comic was removed from the library or now has a different file, so it is skipped. + Un fumetto è stato rimosso dalla libreria o ora ha un file diverso, quindi viene saltato. + + + + + Changes not saved + Modifiche non salvate + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Un fumetto che stai modificando è stato rimosso dalla libreria o ora ha un file diverso. Le modifiche non sono state salvate. + + + + The changes could not be saved. + Impossibile salvare le modifiche. + + + Invalid cover Copertina non valida - + The image is invalid. L'immagine non è valida. - + Synopsis: Sinossi: - + Title: Titolo: - + Not found Non trovato - + Characters: Personaggi: - + Authors Autori - + Age rating: - Valutazione età: + Classificazione per età: - + Story arc: - Arco Narrativo: + Arco narrativo: - + Writer(s): - Scrittore(i): + Sceneggiatore/i: - + Tags: - tag: + Tag: - + Comic not found. You should update your library. - Fumetto non trovato, dovresti aggiornare la Libreria. + Fumetto non trovato. Dovresti aggiornare la libreria. - + Edit comic information - Edita le informazioni del fumetto + Modifica le informazioni del fumetto - + Cover page - Pagina Copertina + Copertina - + Cover Artist(s): Artista(i) copertina: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> Vai </a> + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> vedi </a> - + Volume: - Album: + Volume: - + Format: Formato: - + Letterer(s): Letterista(i): - - + + of: - Di: + di: - + Arc number: Numero dell'arco: @@ -3443,12 +4577,12 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Impossibile caricare la libreria 7z da ./utils - + Select custom cover Seleziona la copertina personalizzata - + Images (%1) Immagini (%1) @@ -3475,37 +4609,172 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Reading Lists - Lista di lettura + Liste di lettura ReadingListManagementCoordinator - + Add new reading lists Aggiungi una lista di lettura - - + + List name: Nome lista: - + + Import reading lists + Importa liste di lettura + + + + Reading lists (*.cbl *.json);;All files (*) + Liste di lettura (*.cbl *.json);;Tutti i file (*) + + + + New smart list + Nuova lista intelligente + + + + + The smart list could not be saved. + Impossibile salvare la lista intelligente. + + + + Edit smart list + Modifica lista intelligente + + + + Delete folder + Cancella cartella + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Eliminare la cartella "%1" e tutto il suo contenuto? Le sue cartelle e liste verranno eliminate, ma i file dei fumetti verranno conservati. + + + Delete list/label - Cancella Lista/Etichetta + Cancella lista/etichetta - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - Gli elementi selezionati verranno cancellati, i tuoi fumetti o cartella NON verranno cancellati dal tuo disco. Sei sicuro? + L'elemento selezionato verrà cancellato; i tuoi fumetti o le tue cartelle NON verranno cancellati dal disco. Sei sicuro? - + Rename list name Rinomina la lista + + + Rename folder + Rinomina cartella + + + + + Folder name: + Nome della cartella: + + + + + + New folder + Nuova cartella + + + + + The folder could not be created. + Impossibile creare la cartella. + + + + Reading list + Lista di lettura + + + + CBL reading list (*.cbl) + Lista di lettura CBL (*.cbl) + + + + JSON reading list (*.json) + Lista di lettura JSON (*.json) + + + + Web page (*.html) + Pagina web (*.html) + + + + + Export reading list + Esporta lista di lettura + + + + Exporting reading list... + Esportazione della lista di lettura... + + + + The file could not be written. + Impossibile scrivere il file. + + + + Move to folder + Sposta nella cartella + + + + "%1" could not be moved. + Impossibile spostare "%1". + + + + ReadingListPage + + + %n comic(s) + + %n fumetto + %n fumetti + + + + + %n missing + + %n mancante + %n mancanti + + + + + Missing + Mancante + + + + Exported from YACReader on %1 + Esportato da YACReader il %1 + RenameLibraryDialog @@ -3517,7 +4786,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Cancel - Cancella + Annulla @@ -3526,7 +4795,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam - New Library Name : + New Library Name: Nome della nuova libreria: @@ -3534,8 +4803,8 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam ScraperResultsPaginator - Number of %1 found : %2 - Numero di %1 trovati; %2 + Number of %1 found: %2 + Numero di %1 trovati: %2 @@ -3545,7 +4814,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam - Number of volumes found : %1 + Number of volumes found: %1 Numero di volumi trovati: %1 @@ -3644,7 +4913,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Editor credit - Credito dell'editor + Credito del curatore @@ -3828,7 +5097,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Please provide some additional information for this comic. Please provide some additional information. - Per favore aggiugi informazioni addizionali. + Per favore aggiungi altre informazioni per questo fumetto. @@ -3838,7 +5107,7 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Use exact match search. Disable if you want to find volumes that match some of the words in the name. - Utilizza la ricerca con corrispondenza esatta. Disabilitare se si desidera trovare volumi che corrispondono ad alcune delle parole nel nome. + Utilizza la ricerca con corrispondenza esatta. Disabilita se desideri trovare volumi che corrispondono ad alcune delle parole nel nome. @@ -3908,6 +5177,11 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Just start typing. Plain text search across all metadata. Inizia a digitare. Il testo semplice viene cercato in tutti i metadati. + + + %1 -> search for `%1` in any database field + %1 -> cerca `%1` in qualsiasi campo del database + 1. Search everywhere @@ -4003,7 +5277,9 @@ Per interrompere un aggiornamento automatico, tocca l'indicatore di caricam Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Suggerimenti: gli spazi funzionano come AND e le ricerche non distinguono tra maiuscole e minuscole. Usa le virgolette per includere spazi in un valore. + Suggerimenti: +Gli spazi funzionano come AND e le ricerche non distinguono tra maiuscole e minuscole. +Usa le virgolette per includere spazi in un valore. @@ -4141,7 +5417,7 @@ Use quotes to include spaces in a value. Please provide some additional information. - Per favore aggiugi informazioni addizionali. + Per favore aggiungi altre informazioni. @@ -4151,7 +5427,7 @@ Use quotes to include spaces in a value. Use exact match search. Disable if you want to find volumes that match some of the words in the name. - Utilizza la ricerca con corrispondenza esatta. Disabilitare se si desidera trovare volumi che corrispondono ad alcune delle parole nel nome. + Utilizza la ricerca con corrispondenza esatta. Disabilita se desideri trovare volumi che corrispondono ad alcune delle parole nel nome. @@ -4169,7 +5445,7 @@ Use quotes to include spaces in a value. loading cover - Caricamento copertine + Caricamento copertina @@ -4178,7 +5454,7 @@ Use quotes to include spaces in a value. - Please, select the right comic info. + Please select the right comic info. Per favore seleziona le informazioni corrette per il fumetto. @@ -4191,7 +5467,7 @@ Use quotes to include spaces in a value. - Please, select the right series for your comic. + Please select the right series for your comic. Per favore seleziona la serie corretta per il fumetto. @@ -4207,7 +5483,7 @@ Use quotes to include spaces in a value. loading cover - Caricamento copertine + Caricamento copertina @@ -4225,54 +5501,59 @@ Use quotes to include spaces in a value. Unknown - Sconosciuto + Sconosciuto %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 sconosciute) %1 series - + %1 serie - + %1 comics selected %n comic(s) selected - Fumetto %1 selezionato + %1 fumetti selezionati - + Read - Leggi + Letti - + In progress - In corso + In corso - + Unread - Non letti + Non letti - + Total pages - + Pagine totali - + Total size - + Dimensione totale + + + + Missing + Mancante - + Series - Serie + Serie @@ -4285,12 +5566,12 @@ Use quotes to include spaces in a value. yes - Si + Sì - You are trying to get information for various comics at once, are they part of the same series? - Stai cercando di recuperare informazioni per diversi fumetti in una sola volta, sono parte della stessa serie? + You are trying to get information for various comics at once. Are they part of the same series? + Stai cercando di recuperare informazioni per diversi fumetti in una sola volta. Fanno parte della stessa serie? @@ -4309,7 +5590,7 @@ Use quotes to include spaces in a value. Devices on this network can reach your library at the address below. - I dispositivi su questa rete possono accedere alla tua libreria all’indirizzo riportato di seguito. + I dispositivi su questa rete possono accedere alla tua libreria all'indirizzo riportato di seguito. @@ -4347,58 +5628,74 @@ Use quotes to include spaces in a value. YACReader è disponibile per iOS e Android. Scoprilo per <a href='https://ios.yacreader.com'>iOS</a> o <a href='https://android.yacreader.com'>Android</a>. - enable the server - Abilita il server + + Set port + set port + Imposta porta + + + SmartListDialog - Server connectivity information - Informazioni sulla connettività del server + + New smart list + Nuova lista intelligente - Scan it! - Scansiona! + + Edit smart list + Modifica lista intelligente - - Set port - set port - Imposta porta + + Search filters and syntax + Filtri e sintassi di ricerca - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader è disponibile per dispositivi iOS e Android.<br/>Scoprilo per <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> o <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Name: + Nome: - Choose an IP address - Scegli un indirizzo IP + + Search: + Ricerca: SortVolumeComics - + remove selected comics Rimuovi i fumetti selezionati - + sort comics to match comic information Ordina i fumetti per far corrispondere le informazioni - + restore all removed comics Ripristina tutti i fumetti rimossi + + + Add the %n other issue(s) of this volume as missing comics + + Aggiungi l'altro numero di questo volume come fumetto mancante (%n) + Aggiungi gli altri %n numeri di questo volume come fumetti mancanti + + - + issues - Emissione + Albi - - Please, sort the list of comics on the left until it matches the comics' information. - Per favore ordina la lista dei fumetti a sinistra sino a che corrisponde alle informazioni dei fumetti. + + Please sort the list of comics on the left until it matches the comics' information. + Per favore ordina la lista dei fumetti a sinistra finché non corrisponde alle informazioni dei fumetti. @@ -4406,7 +5703,7 @@ Use quotes to include spaces in a value. Theme Editor - Redattore del tema + Editor del tema @@ -4421,7 +5718,7 @@ Use quotes to include spaces in a value. i - io + i @@ -4436,7 +5733,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Tieni premuto per far lampeggiare il valore selezionato nell'interfaccia utente (magenta / alternato / 0↔10). Le versioni ripristinano l'originale. + Tieni premuto per far lampeggiare il valore selezionato nell'interfaccia (magenta / alternato / 0↔10). Al rilascio viene ripristinato l'originale. @@ -4446,17 +5743,17 @@ Use quotes to include spaces in a value. Light - Luminoso + Chiaro Dark - Buio + Scuro ID: - Identificativo: + ID: @@ -4506,7 +5803,7 @@ Use quotes to include spaces in a value. Double-click to edit color - Fare doppio clic per modificare il colore + Fai doppio clic per modificare il colore @@ -4516,7 +5813,7 @@ Use quotes to include spaces in a value. true - VERO + true @@ -4524,17 +5821,17 @@ Use quotes to include spaces in a value. false - falso + false Double-click to toggle - Fare doppio clic per attivare/disattivare + Fai doppio clic per attivare/disattivare Double-click to edit value - Fare doppio clic per modificare il valore + Fai doppio clic per modificare il valore @@ -4611,19 +5908,27 @@ Use quotes to include spaces in a value. Update library - Aggiorna Libreria + Aggiorna libreria Cancel - Cancella + Annulla - Updating.... + Updating... Aggiornamento... + + ViewSelector + + + View + + + VolumeComicsModel @@ -4642,12 +5947,12 @@ Use quotes to include spaces in a value. issues - Emissione + Albi publisher - Pubblicato da + editore @@ -4690,12 +5995,12 @@ Use quotes to include spaces in a value. Custom: - Personalizza: + Personalizzato: View angle - Vista ad Angolo + Angolo di vista @@ -4705,7 +6010,7 @@ Use quotes to include spaces in a value. Cover gap - Distanza tra le Cover + Distanza tra le copertine @@ -4715,17 +6020,17 @@ Use quotes to include spaces in a value. Zoom - Ingrandimento + Zoom Y offset - Traslazione Y + Offset Y Z offset - Traslazione Z + Offset Z @@ -4740,7 +6045,7 @@ Use quotes to include spaces in a value. Light - Luminoso + Luce @@ -4750,17 +6055,17 @@ Use quotes to include spaces in a value. Low Performance - Basse Prestazioni + Prestazioni basse High Performance - Alte Prestazioni + Prestazioni alte - Use VSync (improve the image quality in fullscreen mode, worse performance) - UIsa VSync. (Migliora la qualità dell'immagine a modalità tutto schermo, peggiora le prestazioni) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Usa VSync (migliora la qualità dell'immagine a schermo intero, peggiora le prestazioni) @@ -4783,7 +6088,7 @@ Use quotes to include spaces in a value. YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary continuerà a essere eseguito nella barra delle applicazioni. Per terminare il programma, scegli <b>Esci</b> nel menu contestuale dell'icona nella barra delle applicazioni. + YACReaderLibrary continuerà a essere eseguito nell'area di notifica. Per terminare il programma, scegli <b>Esci</b> nel menu contestuale dell'icona nell'area di notifica. @@ -4799,18 +6104,56 @@ Use quotes to include spaces in a value. Versioni precedenti + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Il database della libreria è mancante. Ripristina un backup o ricrea la libreria prima di ripetere la scansione delle informazioni XML. + + + + Unable to load the archive backend: %1 + Impossibile caricare il backend degli archivi: %1 + + + + Unable to start an XML rescan transaction. + Impossibile avviare una transazione per la nuova scansione XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + La cartella selezionata non è più in questa libreria. Ricarica la libreria prima di ripetere la scansione. + + + + Unable to read comics for the XML rescan. + Impossibile leggere i fumetti per la nuova scansione XML. + + + + Unable to commit XML metadata changes. + Impossibile confermare le modifiche ai metadati XML. + + + + Unable to update XML metadata for %1. + Impossibile aggiornare i metadati XML di %1. + + YACReaderFieldEdit Restore to default - Resetta al Default + Ripristina il valore predefinito - + Click to overwrite - Clikka per sovrascrivere + Clicca per sovrascrivere @@ -4818,15 +6161,17 @@ Use quotes to include spaces in a value. Restore to default - Resetta al Default + Ripristina il valore predefinito + + - - - + + + Click to overwrite - Clikka per sovrascrivere + Clicca per sovrascrivere @@ -4839,7 +6184,7 @@ Use quotes to include spaces in a value. Cancel - Cancella + Annulla @@ -4859,7 +6204,7 @@ Use quotes to include spaces in a value. Edit shortcuts - Modifica scorciatoia + Modifica scorciatoie diff --git a/YACReaderLibrary/yacreaderlibrary_ko.ts b/YACReaderLibrary/yacreaderlibrary_ko.ts index cb9c27628..55854b844 100644 --- a/YACReaderLibrary/yacreaderlibrary_ko.ts +++ b/YACReaderLibrary/yacreaderlibrary_ko.ts @@ -11,6 +11,66 @@ AddLabelDialog + + + red + 빨강 + + + + orange + 주황 + + + + yellow + 노랑 + + + + green + 초록 + + + + cyan + 청록 + + + + blue + 파랑 + + + + violet + 제비꽃색 + + + + purple + 자주 + + + + pink + 분홍 + + + + white + 흰색 + + + + light + 밝음 + + + + dark + 어두움 + Label name: @@ -51,12 +111,12 @@ - Comics folder : + Comics folder: 만화 폴더: - Library name : + Library name: 라이브러리 이름: @@ -69,8 +129,8 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Comic Vine에 연결하려면 본인의 API 키가 필요합니다. <a href="http://www.comicvine.com/api/">여기</a>에서 무료로 발급받으세요. + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Comic Vine에 연결하려면 본인의 API 키가 필요합니다. <a href="http://www.comicvine.com/api/">여기</a>에서 무료로 발급받으세요 @@ -118,7 +178,7 @@ Remove this user-imported theme - 사용자 가져온 이 테마 제거 + 사용자가 가져온 이 테마 제거 @@ -200,103 +260,620 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - 만화 흐름 숨기기 + + Find comic › + 만화 찾기 › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - 만화 복사 중... + + Series + 시리즈 - Moving comics... - 만화 이동 중... + + Number + 번호 + + + + Volume + 볼륨 + + + + Year + 연도 + + + + Format + 형식 + + + + + ComicVine ID + Comic Vine ID + + + + series and number + 시리즈 및 번호 + + + + series without the volume, and number + 권을 제외한 시리즈 및 번호 + + + + letters and digits of the series, and number + 시리즈의 문자와 숫자 및 번호 + + + + series with its year, and number + 연도를 포함한 시리즈 및 번호 + + + + volume %1 + %1권 + + + + Comic in your library + 라이브러리의 만화 + + + + Missing in your library + 라이브러리에 없음 + + + + Changed by you. + 사용자가 변경했습니다. + + + + The entry has this comic. + 이 항목에 이 만화가 연결되어 있습니다. + + + + Matched by %1. + %1 기준으로 일치합니다. + + + + Your library already has this comic as missing. Matched by %1. + 라이브러리에 이미 누락된 만화로 등록되어 있습니다. %1 기준으로 일치합니다. + + + + Possible comic %1 + 일치 가능성이 있는 만화 %1 + + + + + Changed by you: it is imported as missing. + 사용자가 변경했습니다. 누락된 만화로 가져옵니다. + + + + The comics have the same ComicVine ID. + 만화의 Comic Vine ID가 같습니다. + + + + The comic has the same series and number, but its volume or year is different. + 시리즈와 번호는 같지만 권 또는 연도가 다릅니다. + + + + The comics have the same series and number. + 만화의 시리즈와 번호가 같습니다. + + + + Values that are different from the file are in amber. + 파일과 다른 값은 주황색으로 표시됩니다. + + + + Closest series in your library + 라이브러리에서 가장 유사한 시리즈 + + + + Library + 라이브러리 + + + + Has %1 + %1 있음 + + + + No comic found. Tried: %1. + 만화를 찾지 못했습니다. 검색 기준: %1. + + + + In the file + 파일 내 정보 + + + + Entry %1 of %2 + 항목 %1 / %2 + + + + Use this comic + 이 만화 사용 + + + + Use this missing comic + 이 누락된 만화 사용 + + + + CblFindComicPage + + + ‹ All entries + ‹ 모든 항목 + + + + Previous problem + 이전 문제 + + + + Next problem + 다음 문제 + + + + Folders + 폴더 + + + + Search the whole library + 전체 라이브러리 검색 + + + + File + 파일 + + + + Series + 시리즈 + + + + No. + 번호 + + + + Volume + 볼륨 + + + + Year + 연도 + + + + Go to the next problem after I choose + 선택 후 다음 문제로 이동 + + + + Use this comic + 이 만화 사용 + + + + Cancel + 취소 + + + + Search in this folder + 이 폴더에서 검색 + + + + Entry %1 of %2 + 항목 %1 / %2 + + + + Missing: no comic found. Browse or search the library. + 누락: 만화를 찾지 못했습니다. 라이브러리를 탐색하거나 검색하세요. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + 모호함: 일치 가능성이 있는 만화 %n개. 라이브러리를 탐색하거나 검색하세요. + + + + + Browse or search the library to choose another comic. + 다른 만화를 선택하려면 라이브러리를 탐색하거나 검색하세요. + + + + Problem %1 of %2 + 문제 %1 / %2 + + + + %n problem(s) + + 문제 %n개 + + + + + + + Library + 라이브러리 + + + + Search results + 검색 결과 + + + + CblImportDialog + + + Match to library comics — %1 + 라이브러리 만화와 연결 — %1 + + + + Apply + 적용 + + + + Close + 닫기 + + + + Skip + 건너뛰기 + + + + Import + 가져오기 + + + + &Name: + 이름(&N): + + + + A reading list with this name already exists. Import creates another one. + 같은 이름의 읽기 목록이 이미 있습니다. 가져오면 새 목록이 생성됩니다. + + + + Filter entries + 항목 필터링 + + + + Matching the books of %1 with your library... + %1의 책을 라이브러리와 연결하는 중... + + + + Matching the entries of %1 with your library... + %1의 항목을 라이브러리와 연결하는 중... + + + + + Match to library comics + 라이브러리 만화와 연결 + + + + This also gives comics to entries in %n other list(s): %1 + + 다른 목록 %n개의 항목에도 만화가 연결됩니다: %1 + + + + + The changes could not be applied. Nothing was changed. + 변경 사항을 적용하지 못했습니다. 아무것도 변경되지 않았습니다. + + + + The file cannot be opened. + 파일을 열 수 없습니다. + + + + The file is damaged (line %1, column %2). + 파일이 손상되었습니다(%1행, %2열). + + + + The file is not a reading list. + 읽기 목록 파일이 아닙니다. + + + + The library cannot be read. + 라이브러리를 읽을 수 없습니다. + + + + Import reading lists + 읽기 목록 가져오기 + + + + The reading list could not be imported. Nothing was written. + 읽기 목록을 가져오지 못했습니다. 아무것도 기록되지 않았습니다. + + + + Import reading lists — %1 (%2 of %3) + 읽기 목록 가져오기 — %1(%2 / %3) + + + + Import reading lists — %1 + 읽기 목록 가져오기 — %1 + + + + All (%1) + 전체(%1) + + + + Matched (%1) + 일치(%1) + + + + Ambiguous (%1) + 모호함(%1) + + + + Missing (%1) + 누락(%1) + + + + Changed by you (%1) + 사용자가 변경함(%1) + + + + %1 of %n have a comic + + %n개 중 %1개에 만화가 연결됨 + + + + + Find comic... + 만화 찾기... + + + + Use the original match + 원래 일치 항목 사용 + + + + Make missing + 누락으로 표시 + + + + Import as missing + 누락으로 가져오기 + + + + CblImportModel + + + Matched + 일치 + + + + Ambiguous + 모호함 + + + + Missing + 누락 + + + + Changed + 변경됨 + + + + # + # + + + + In the file + 파일 내 정보 + + + + Comic in your library + 라이브러리의 만화 + + + + Status + 상태 + + + + Already missing in your library + 라이브러리에 이미 누락으로 등록됨 + + + + %n possible comic(s) + + 일치 가능성이 있는 만화 %n개 + + + + + Not in library + 라이브러리에 없음 + + + + CblMatchComicDialog + + + Match to library comic + 라이브러리 만화와 연결 + + + + Loading the library... + 라이브러리 불러오는 중... + + + + The library cannot be read. + 라이브러리를 읽을 수 없습니다. + + + + ClassicComicsView + + + Hide comic flow + Comic Flow 숨기기 ComicInfoView - + Characters 등장인물 - + Main character or team 주요 등장인물 또는 팀 - + Teams 팀 - + Locations 장소 - + Authors 작가진 - + writer 글 - + penciller 밑그림 - + inker 펜선 - + colorist 채색 - + letterer 식자 - + cover artist 표지 그림 - + editor 편집 - + imprint 출판 브랜드 - + Publisher 출판사 - + color 컬러 - + b/w 흑백 @@ -304,28 +881,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader를 찾을 수 없음 - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader를 찾을 수 없습니다. YACReader는 YACReaderLibrary와 같은 폴더에 설치되어야 합니다. - + YACReader not found. There might be a problem with your YACReader installation. YACReader를 찾을 수 없습니다. YACReader 설치에 문제가 있을 수 있습니다. - + Error 오류 - + Error opening comic with third party reader. 타사 뷰어로 만화를 여는 중 오류가 발생했습니다. @@ -333,67 +910,67 @@ ComicModel - + yes 예 - + no 아니오 - + Title 제목 - + File Name 파일 이름 - + Pages 페이지 - + Size 크기 - + Read 읽음 - + Current Page 현재 페이지 - + Publication Date 출판일 - + Rating 평점 - + Series 시리즈 - + Volume 볼륨 - + Story Arc 스토리 아크 @@ -401,66 +978,107 @@ ComicVineDialog - + skip 건너뛰기 - + back 뒤로 - + next 다음 - + search 검색 - + close 닫기 - - - + + + Looking for volume... 볼륨 검색 중... - - + + comic %1 of %2 - %3 %1 / %2 만화 - %3 - + %1 comics selected 만화 %1개 선택됨 - - Error connecting to ComicVine + + Error connecting to Comic Vine Comic Vine 연결 오류 - - - Retrieving tags for : %1 - 태그 가져오는 중 : %1 + + + Retrieving tags for: %1 + 태그 가져오는 중: %1 + + + + Creating missing comic %1 of %2 + 누락된 만화 %1 / %2 생성 중 + + + + Comic Vine did not answer for %n comic(s). + + 만화 %n개에 대해 Comic Vine의 응답이 없습니다. + + + + + %n missing comic(s) got only the basic data of the volume. + + 누락된 만화 %n개에 권의 기본 정보만 적용되었습니다. + + + + + %n missing comic(s) have no cover. + + 누락된 만화 %n개에 표지가 없습니다. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine은 시간당 약 200건의 요청을 허용합니다. 업데이트되지 않은 만화는 나중에 다시 시도하세요. + + + + Try again later for the comics that were not updated. + 업데이트되지 않은 만화는 나중에 다시 시도하세요. + + + + Comic Vine + Comic Vine - + Retrieving volume info... 볼륨 정보 가져오는 중... - + Looking for comic... 만화 검색 중... @@ -492,17 +1110,17 @@ - Comics folder : + Comics folder: 만화 폴더: - Library Name : + Library Name: 라이브러리 이름: - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. 라이브러리 생성은 몇 분 걸릴 수 있습니다. 작업을 중지하고 나중에 라이브러리를 업데이트하여 완료할 수 있습니다. @@ -519,11 +1137,19 @@ DBHelper - + The folder entry could not be found in the library database. 라이브러리 데이터베이스에서 폴더 항목을 찾을 수 없습니다. + + DelayedProgressDialog + + + Cancel + 취소 + + EditShortcutsDialog @@ -533,8 +1159,8 @@ - To change a shortcut, double click in the key combination and type the new keys. - 단축키를 바꾸려면 키 조합을 더블 클릭하고 새 키를 입력하세요. + To change a shortcut, double-click the key combination and type the new keys. + 단축키를 바꾸려면 키 조합을 두 번 클릭하고 새 키를 입력하세요. @@ -548,7 +1174,7 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function "%1" 단축키는 이미 다른 기능이 사용 중입니다 @@ -581,6 +1207,14 @@ 이 라벨에 아직 만화가 없습니다 + + EmptyListFolderWidget + + + This folder is empty + 이 폴더는 비어 있습니다 + + EmptyReadingListWidget @@ -604,7 +1238,7 @@ There are no recent comics! - 최근 본 만화가 없습니다! + 최근 추가된 만화가 없습니다! @@ -621,7 +1255,7 @@ - Output file : + Output file: 출력 파일: @@ -659,7 +1293,7 @@ - Output folder : + Output folder: 출력 폴더: @@ -686,34 +1320,27 @@ FileComic - + 7z not found 7z를 찾을 수 없습니다 - + CRC error on page (%1): some of the pages will not be displayed correctly %1번 페이지에서 CRC 오류 발생: 일부 페이지가 올바르게 표시되지 않을 수 있습니다 - + Unknown error opening the file 파일을 여는 중 알 수 없는 오류가 발생했습니다 - - + + Format not supported 지원하지 않는 형식입니다 - - FolderContentView - - Continue Reading... - 이어 읽기... - - FolderInfoView @@ -788,53 +1415,37 @@ GridComicsView - + Show info 정보 보기 - Library - 라이브러리 - - - Folder - 폴더 - - - Favorites - 즐겨찾기 - - - Recently added - 최근 추가 - - - + Manga 망가 - + Western manga - 서양식 망가 + 서양 만화 - + Web comic 웹툰 - + Yonkoma 4컷 만화 - + Comic 만화 - + Unknown 알 수 없음 @@ -876,7 +1487,7 @@ - Info database location : + Info database location: 정보 데이터베이스 경로: @@ -894,7 +1505,7 @@ ImportLibraryDialog - Destination folder : + Destination folder: 대상 폴더: @@ -909,17 +1520,17 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) 압축된 라이브러리 표지 (*.clc) - Package location : + Package location: 패키지 경로: - Library Name : + Library Name: 라이브러리 이름: @@ -947,7 +1558,7 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> <p>YACReaderLibrary가 새 라이브러리를 만들고 있습니다.</p><p>라이브러리 생성은 몇 분 걸릴 수 있습니다. 작업을 중지하고 나중에 라이브러리를 업데이트하여 작업을 완료할 수 있습니다.</p> @@ -957,7 +1568,7 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> <p>현재 라이브러리를 업데이트하고 있습니다. 더 빠른 업데이트를 위해 라이브러리를 자주 업데이트하세요.</p><p>작업을 중지하고 나중에 이 라이브러리 업데이트를 계속할 수 있습니다.</p> @@ -971,19 +1582,49 @@ <p>현재 라이브러리를 업그레이드하고 있습니다. 잠시만 기다려주세요.</p> - - Scanning the library - 라이브러리 스캔 중 + + Scanning the library + 라이브러리 스캔 중 + + + + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>현재 라이브러리에서 레거시 XML 메타데이터 정보를 스캔하고 있습니다.</p><p>이 작업은 한 번만 필요하며, 라이브러리가 YACReaderLibrary 9.8.2 또는 이전 버전으로 만들어진 경우에만 해당됩니다.</p> + + + + <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> + <p>현재 라이브러리에서 누락된 표지와 불완전한 만화 정보를 확인하고 있습니다.</p><p>몇 분 정도 걸릴 수 있습니다. 작업을 중지하고 나중에 다시 실행할 수 있습니다.</p> + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 라이브러리 데이터 폴더를 만들 수 없습니다: %1. 폴더 접근 권한과 디스크 여유 공간을 확인하세요. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 라이브러리 데이터 폴더가 없습니다: %1. 업데이트하기 전에 폴더를 복원하거나 라이브러리를 다시 만드세요. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 라이브러리 데이터베이스가 없습니다: %1. 업데이트하기 전에 백업을 복원하거나 라이브러리를 다시 만드세요. - - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>현재 라이브러리에서 레거시 XML 메타데이터 정보를 스캔하고 있습니다.</p><p>이 작업은 한 번만 필요하며, 라이브러리가 YACReaderLibrary 9.8.2 또는 이전 버전으로 만들어진 경우에만 해당됩니다.</p> + + + Unable to start a library database transaction: %1 + 라이브러리 데이터베이스 트랜잭션을 시작할 수 없습니다: %1 - - <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> - <p>현재 라이브러리에서 누락된 표지와 불완전한 만화 정보를 확인하고 있습니다.</p><p>몇 분 정도 걸릴 수 있습니다. 작업을 중지하고 나중에 다시 실행할 수 있습니다.</p> + + + Unable to commit the library database: %1 + 라이브러리 데이터베이스 트랜잭션을 커밋할 수 없습니다: %1 @@ -1009,108 +1650,201 @@ 읽은 만화 수 + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + 이 라이브러리는 사용 중입니다. 유지 관리가 끝날 때까지 기다리세요. 멈춘 것 같으면 세부 정보에 표시된 앱을 닫고 다시 시도하세요. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + 이전 유지 관리 작업에서 라이브러리를 해제하지 못했을 수 있습니다. 접근 권한을 복구하기 전에 다른 컴퓨터를 포함하여 YACReaderLibrary 또는 YACReaderLibraryServer의 어떤 작업도 이 라이브러리를 사용하고 있지 않은지 확인하세요. + + + + Retry + 다시 시도 + + + + Recover and retry + 복구 후 다시 시도 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 라이브러리 잠금 소유자가 변경되었습니다. 작업을 다시 시도하고 새 소유자를 확인한 후 접근 권한을 복구하세요. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 유지 관리 잠금을 제거할 수 없습니다: %1. 파일이 읽기 전용이 아니며 라이브러리 데이터 폴더에 쓰기 권한이 있는지 확인한 후 다시 시도하세요. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 유지 관리 잠금 파일 경로를 폴더가 차지하고 있습니다: %1. 폴더 이름을 바꾸거나 이동한 후 다시 시도하세요. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 라이브러리 잠금 파일을 만들 수 없습니다: %1. 라이브러리 데이터 폴더의 쓰기 권한을 확인하세요. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 라이브러리 데이터 폴더가 없습니다: %1. 폴더를 복원하거나 라이브러리를 다시 만든 후 업데이트하세요. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 이 라이브러리의 유지 관리 잠금 소유자를 확인할 수 없습니다(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 다른 유지 관리 작업에서 이 라이브러리를 사용 중입니다(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 라이브러리 잠금 파일을 만들 수 없습니다: %1. 디스크 여유 공간과 라이브러리 데이터 폴더 접근 권한을 확인하세요. + + LibraryManagementCoordinator - + Error opening the library 라이브러리 열기 오류 - + Error creating the library 라이브러리 생성 오류 - + Error updating the library 라이브러리 업데이트 오류 + + + + XML information rescan + XML 정보 다시 검색 + + + + Restore recovery + 복원 복구 + + + + Library upgrade + 라이브러리 업그레이드 + + + + Library maintenance + 라이브러리 유지 관리 + + + + Recover library access + 라이브러리 접근 복구 + LibraryWindow - - Do you want remove - 다음을 제거하시겠습니까: + + Do you want to remove the library '%1'? + '%1' 라이브러리를 제거하시겠습니까? - + YACReader Library YACReader Library - + Are you sure? 확실합니까? + Delete folder 폴더 삭제 - + Upgrade failed 업그레이드 실패 - + There were errors during library upgrade in: 라이브러리 업그레이드 중 오류 발생: - + Restore recovery failed 복원 복구 실패 - + Update needed 업데이트 필요 - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? 이 라이브러리는 YACReaderLibrary의 이전 버전으로 만들어졌습니다. 업데이트가 필요합니다. 지금 업데이트하시겠습니까? - + Download new version 새 버전 내려받기 - + This library was created with a newer version of YACReaderLibrary. Download the new version now? 이 라이브러리는 YACReaderLibrary의 최신 버전으로 만들어졌습니다. 지금 새 버전을 내려받으시겠습니까? - + Library not available 라이브러리를 사용할 수 없습니다 - + Library '%1' is no longer available. Do you want to remove it? '%1' 라이브러리를 더 이상 사용할 수 없습니다. 제거하시겠습니까? - + Old library 오래된 라이브러리 - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? '%1' 라이브러리는 이전 버전의 YACReaderLibrary로 만들어졌습니다. 다시 만들어야 합니다. 지금 만드시겠습니까? - + Copying comics... 만화 복사 중... - + Moving comics... 만화 이동 중... @@ -1130,7 +1864,7 @@ - Please, select a folder first + Please select a folder first 먼저 폴더를 선택하세요 @@ -1149,84 +1883,113 @@ 선택한 폴더와 그 안의 모든 내용이 디스크에서 삭제됩니다. 계속하시겠습니까? - + Unable to delete 삭제할 수 없음 - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. 선택한 폴더를 삭제하는 중 문제가 발생했습니다. 쓰기 권한을 확인하고, 다른 응용 프로그램이 이 폴더나 안의 파일을 사용하고 있지 않은지 확인하세요. - + + Edit smart list... + 스마트 목록 편집... + + + + Delete smart list + 스마트 목록 삭제 + + + + + + New folder... + 새 폴더... + + + + + + Move to folder... + 폴더로 이동... + + + + Rename folder... + 폴더 이름 바꾸기... + + + + Match to library comics... + 라이브러리 만화와 연결... + + + + Track missing issues... + 누락된 호 추적... + + + + Export reading list... + 읽기 목록 내보내기... + + + Rename or organize files 파일 이름 변경 또는 정리 - + Set the type of the selected comics 선택한 만화의 유형 설정 - + + Save as smart list… + 스마트 목록으로 저장… + + + Search filters 검색 필터 - + Unread 읽지 않음 - + In progress 읽는 중 - + Highly rated 높은 평점 - + Recently added 최근 추가 - + Search syntax… 검색 구문… - - A repair of this library is already running (%1). Wait for it to finish. - 이 라이브러리에 대한 복구가 이미 진행 중입니다 (%1). 완료될 때까지 기다려 주세요. - - - - The library is locked by a repair that did not finish. - 라이브러리가 완료되지 않은 복구에 의해 잠겨 있습니다. - - - - The library is locked by a repair started by %1. - 라이브러리가 %1에서 시작한 복구에 의해 잠겨 있습니다. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - 다른 복구가 실행 중이 아니라고 확신하면 잠금을 해제할 수 있습니다. 잠금을 해제하고 계속하시겠습니까? - - - + Package operation failed 패키지 작업 실패 - + The covers package operation could not be completed. 표지 패키지 작업을 완료할 수 없습니다. @@ -1277,22 +2040,22 @@ Folder: %1 라이브러리 데이터베이스를 업데이트하지 못했고 디스크의 폴더 이름 변경도 되돌리지 못했습니다. 이제 라이브러리를 수동으로 업데이트해야 합니다. - + Save covers 표지 저장 - + You are adding too many libraries. 라이브러리를 너무 많이 추가하고 있습니다. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. 라이브러리를 너무 많이 추가하고 있습니다. 최상위 만화 폴더에 라이브러리 하나만 있으면 충분합니다. 좌측 사이드바의 폴더 섹션으로 하위 폴더를 탐색할 수 있습니다. @@ -1300,58 +2063,52 @@ YACReaderLibrary will not stop you from creating more libraries but you should k YACReaderLibrary는 라이브러리를 더 만드는 것을 막지 않지만, 라이브러리 수는 적게 유지하는 것이 좋습니다. - + Library not found 라이브러리를 찾을 수 없음 - + The selected folder doesn't contain any library. 선택한 폴더에 라이브러리가 없습니다. - - + + YACReader library database (*.ydb) YACReader 라이브러리 데이터베이스 (*.ydb) - + The library database backup was created at: %1 라이브러리 데이터베이스 백업을 다음 위치에 만들었습니다: %1 - + Unable to create the library database backup: %1 라이브러리 데이터베이스 백업을 만들 수 없습니다: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? 복원하기 전에 YACReaderLibraryServer와 이 라이브러리를 사용하는 다른 모든 YACReader 애플리케이션을 종료하세요. 계속하시겠습니까? - + Restoring library database... 라이브러리 데이터베이스 복원 중... - + The current library database is invalid. Restore the selected backup anyway? 현재 라이브러리 데이터베이스가 유효하지 않습니다. 선택한 백업을 그래도 복원하시겠습니까? - - - The library maintenance lock may be stale. Remove it and retry? - 라이브러리 유지 관리 잠금이 오래된 것일 수 있습니다. 잠금을 제거하고 다시 시도하시겠습니까? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1360,71 +2117,77 @@ Restart YACReaderLibrary before attempting recovery again. 복구를 다시 시도하기 전에 YACReaderLibrary를 다시 시작하세요. - + The library database was restored successfully. Update the library now? 라이브러리 데이터베이스를 성공적으로 복원했습니다. 지금 라이브러리를 업데이트하시겠습니까? - + Library database damaged 라이브러리 데이터베이스 손상 - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. '%1' 라이브러리의 데이터베이스가 손상되어 일반 업데이트, 유지 관리 및 백업을 사용할 수 없습니다. YACReader가 데이터베이스 복구를 시도할 수 있습니다. 손상된 일부 데이터는 복구하지 못할 수 있습니다. 기존 백업은 변경되지 않습니다. - + Attempt repair 복구 시도 - + Restore a backup... 백업 복원... - + Repairing library database... 라이브러리 데이터베이스 복구 중... - - + + + Library database repair 라이브러리 데이터베이스 복구 - - Another maintenance operation is currently using this library. Try again after it finishes. - 현재 다른 유지 관리 작업에서 이 라이브러리를 사용 중입니다. 작업이 끝난 후 다시 시도하세요. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + '%1' 라이브러리의 데이터베이스를 검사하고 필요하면 복구를 시도할까요? 기존 백업은 변경되지 않습니다. - + + Check and repair + 검사 및 복구 + + + The library database is already valid. 라이브러리 데이터베이스가 이미 유효합니다. - + Library database repaired 라이브러리 데이터베이스 복구됨 - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 인덱스를 다시 빌드하여 라이브러리 데이터베이스를 복구했습니다. 손상된 원본은 다음 위치에 보존되었습니다: %1 - + Library database rebuilt 라이브러리 데이터베이스 재구축됨 - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1435,7 +2198,7 @@ Update the library now? 지금 라이브러리를 업데이트하시겠습니까? - + The damaged original was preserved at: @@ -1446,12 +2209,12 @@ The damaged original was preserved at: %1 - + Library database repair failed 라이브러리 데이터베이스 복구 실패 - + The library database could not be repaired: %1%2 @@ -1462,95 +2225,126 @@ You can restore a backup from the Library menu or recreate the library. 라이브러리 메뉴에서 백업을 복원하거나 라이브러리를 다시 만들 수 있습니다. - - library? - 라이브러리? - - - + Remove and delete metadata and backups 메타데이터 및 백업 제거 후 삭제 - Remove and delete metadata - 제거 및 메타데이터 삭제 - - - + Library info 라이브러리 정보 - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. 선택한 만화를 삭제하는 중 문제가 발생했습니다. 선택한 파일이나 포함된 폴더의 쓰기 권한을 확인하세요. - + Assign comics numbers 만화에 번호 부여 - - Assign numbers starting in: + + Assign numbers starting at: 다음 번호부터 부여: - + Invalid image 잘못된 이미지 - + The selected file is not a valid image. 선택한 파일이 유효한 이미지가 아닙니다. - + + Error saving cover 표지 저장 오류 - + + There was an error saving the cover image. 표지 이미지를 저장하는 중 오류가 발생했습니다. - + + + Match to library comic + 라이브러리 만화와 연결 + + + + This also gives the comic to entries in %n other list(s): %1 + + 다른 목록 %n개의 항목에도 이 만화가 연결됩니다: %1 + + + + + The comic could not be matched. Nothing was changed. + 만화를 연결하지 못했습니다. 아무것도 변경되지 않았습니다. + + + + The selected missing comics will be removed from this folder. Are you sure? + 선택한 누락된 만화가 이 폴더에서 제거됩니다. 계속할까요? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + 선택한 만화는 디스크에서 삭제되고 선택한 누락된 만화는 이 폴더에서 제거됩니다. 계속할까요? + + + Delete comics 만화 삭제 - + All the selected comics will be deleted from your disk. Are you sure? 선택한 만화가 모두 디스크에서 삭제됩니다. 확실합니까? - + + Remove the comics from Reading? Their reading progress will be reset. + 읽는 중에서 만화를 제거할까요? 읽기 진행 상황이 초기화됩니다. + + + + The comics will be removed from Favorites. Are you sure? + 만화가 즐겨찾기에서 제거됩니다. 계속할까요? + + + Remove comics 만화 제거 - + Comics will only be deleted from the current label/list. Are you sure? 만화가 현재 라벨/목록에서만 삭제됩니다. 확실합니까? - + Library name already exists 라이브러리 이름 중복 - + There is another library with the name '%1'. '%1' 이름의 라이브러리가 이미 있습니다. - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1635,433 +2429,504 @@ Missing files: %3 + Repair library database... + 라이브러리 데이터베이스 복구... + + + + Check the current library database and attempt to repair any damage + 현재 라이브러리 데이터베이스를 검사하고 손상 복구 시도 + + + + Recover library access... + 라이브러리 접근 복구... + + + + Recover access after an interrupted library maintenance operation + 중단된 라이브러리 유지 관리 작업 후 접근 복구 + + + Repair covers and comic info 표지 및 만화 정보 복구 - + Retry comics with missing covers or incomplete information 표지가 없거나 정보가 불완전한 만화를 다시 처리합니다 - + Rename library 라이브러리 이름 변경 - + Rename current library 현재 라이브러리 이름 변경 - + Remove library 라이브러리 제거 - + Remove current library from your collection 내 컬렉션에서 현재 라이브러리 제거 - + Rescan library for XML info - XML 정보로 라이브러리 재검색 + 라이브러리에서 XML 정보 다시 검색 - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. 만화 파일에 포함된 XML 정보를 찾으려고 시도합니다. 9.8.2 이하 버전으로 만든 라이브러리이거나 타사 소프트웨어로 파일에 XML 정보를 포함한 경우에만 필요합니다. - + Open library folder... 라이브러리 폴더 열기... - + Open the root folder of the current library 현재 라이브러리의 루트 폴더 열기 - + Show library info 라이브러리 정보 표시 - + Show information about the current library 현재 라이브러리에 대한 정보 표시 - + Open current comic 현재 만화 열기 - - Open current comic on YACReader + + Open current comic in YACReader YACReader에서 현재 만화 열기 - + Save selected covers to... 선택한 표지 저장... - + Save covers of the selected comics as JPG files 선택한 만화의 표지를 JPG 파일로 저장 - - + + Set as read 읽음으로 표시 - + Set comic as read 만화를 읽음으로 표시 - - + + Set as unread 읽지 않음으로 표시 - + Set comic as unread 만화를 읽지 않음으로 표시 - - + + manga 망가 - + Set issue as manga 만화를 망가로 설정 - - + + comic 만화 - + Set issue as normal 만화를 일반으로 설정 - + western manga 서양 만화 - + Set issue as western manga 만화를 서양 만화로 설정 - - + + web comic 웹 만화 - + Set issue as web comic 만화를 웹 만화로 설정 - - + + yonkoma 4컷 만화 - + Set issue as yonkoma 만화를 4컷 만화로 설정 - + Show/Hide marks 읽음 마크 표시/숨김 - + Show or hide read marks 읽음 마크를 표시하거나 숨김 - + Show/Hide recent indicator - 신규 표시 표시/숨김 + 최근 추가 표시 보이기/숨기기 - + Show or hide recent indicator - 신규 표시를 표시하거나 숨김 + 최근 추가 표시를 보이거나 숨김 - - + + Fullscreen mode on/off - 전체화면 모드 켜기/끄기 + 전체 화면 모드 켜기/끄기 - + Help, About YACReader 도움말, YACReader 정보 - + Add new folder 새 폴더 추가 - + Add new folder to the current library 현재 라이브러리에 새 폴더 추가 - + Rename folder 폴더 이름 바꾸기 - + Rename the current folder on disk and in the library 디스크와 라이브러리에서 현재 폴더 이름 변경 - + Delete folder 폴더 삭제 - + Delete current folder from disk 현재 폴더를 디스크에서 삭제 - + Select root node 루트 노드 선택 - + Expand all nodes 모든 노드 펼치기 - + Collapse all nodes 모든 노드 접기 - + Show options dialog 환경설정 다이얼로그 표시 - + Show comics server options dialog 만화 서버 환경설정 다이얼로그 표시 - - + + Change between comics views 만화 보기 전환 - + + Grid view + 격자 보기 + + + + Flow view + + + + + Info view + + + + Open folder... 폴더 열기... - - + + Rename files... Organize files 파일 이름 변경... - - + + Organize into folders... 폴더로 정리... - + Set as uncompleted 미완료로 표시 - + Set as completed 완료로 표시 - + Set custom cover 사용자 지정 표지 설정 - + Delete custom cover 사용자 지정 표지 삭제 - + western manga (left to right) 서양 만화 (왼쪽 → 오른쪽) - + Open containing folder... - 포함된 폴더 열기... + 상위 폴더 열기... - Reset comic rating - 만화 평점 초기화 + + New reading list + 새 읽기 목록 - + Select all comics 모든 만화 선택 - + Edit 편집 - + Assign current order to comics 만화에 현재 순서 적용 - + Update cover 표지 업데이트 - + Delete selected comics 선택한 만화 삭제 - + Delete metadata from selected comics 선택한 만화에서 메타데이터 삭제 - + Download tags from Comic Vine Comic Vine에서 태그 내려받기 - + Focus search line 검색창으로 이동 - + Focus comics view 만화 보기로 이동 - + Edit shortcuts 단축키 편집 - + &Quit 끝내기(&Q) - + Update folder 폴더 업데이트 - + Update current folder 현재 폴더 업데이트 - + Scan legacy XML metadata 레거시 XML 메타데이터 스캔 - - Add new reading list - 새 읽기 목록 추가 - - - + Add a new reading list to the current library 현재 라이브러리에 새 읽기 목록 추가 - + + New smart list... + 새 스마트 목록... + + + + Add a saved search to the current library + 현재 라이브러리에 저장된 검색 추가 + + + + New folder... + 새 폴더... + + + + Add a folder for reading lists or smart lists + 읽기 목록 또는 스마트 목록용 폴더 추가 + + + + Import reading lists... + 읽기 목록 가져오기... + + + + Import CBL reading lists into the current library + 현재 라이브러리에 CBL 읽기 목록 가져오기 + + + + New + 새로 만들기 + + + + Add or import reading lists + 읽기 목록 추가 또는 가져오기 + + + Remove reading list 읽기 목록 제거 - + Remove current reading list from the library 라이브러리에서 현재 읽기 목록 제거 - + Add new label 새 라벨 추가 - + Add a new label to this library 이 라이브러리에 새 라벨 추가 - + Rename selected list 선택한 목록 이름 변경 - + Rename any selected labels or lists 선택한 라벨이나 목록 이름 변경 - + Add to... 추가... - + Favorites 즐겨찾기 - + Add selected comics to favorites list 선택한 만화를 즐겨찾기 목록에 추가 - - + + Reset rating 평점 초기화 @@ -2090,91 +2955,104 @@ Missing files: %3 - 4koma (top to botom) + 4koma (top to bottom) 4컷 (위 → 아래) - - - + + + Set type 유형 설정 - + Library 라이브러리 - + Folder 폴더 - + Comic 만화 - + + Match to library comic... + 라이브러리 만화와 연결... + + + + + + + Track missing issues... + 누락된 호 추적... + + + Open folder... 폴더 열기... - + Update folder 폴더 업데이트 - + Rename folder 폴더 이름 바꾸기 - + Rename files... 파일 이름 변경... - + Organize into folders... 폴더로 정리... - + Rescan library for XML info - XML 정보로 라이브러리 재검색 + 라이브러리에서 XML 정보 다시 검색 - + Set as uncompleted 미완료로 표시 - + Set as completed 완료로 표시 - + Set as read 읽음으로 표시 - - + + Set as unread 읽지 않음으로 표시 - + Set custom cover 사용자 지정 표지 설정 - - + + Delete custom cover 사용자 지정 표지 삭제 @@ -2207,17 +3085,63 @@ Missing files: %3 하위 목록 1개 - - %1 sublists - 하위 목록 %1개 + + %1 sublists + 하위 목록 %1개 + + + + LocalComicListModel + + + file name + 파일 이름 + + + + MoveToFolderDialog + + + Move to folder + 폴더로 이동 + + + + Top level + 최상위 + + + + Move "%1" to: + "%1" 이동 위치: + + + + NewListFolderDialog + + + New folder + 새 폴더 + + + + Reading lists + 읽기 목록 + + + + Smart lists + 스마트 목록 + + + + Folder name: + 폴더 이름: - - - LocalComicListModel - - file name - 파일 이름 + + For: + 대상: @@ -2229,7 +3153,7 @@ Missing files: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> <p>아무 폴더에 라이브러리를 만들 수 있고, YACReaderLibrary가 그 폴더의 모든 만화와 하위 폴더를 가져옵니다. 이전에 만든 라이브러리가 있으면 열 수 있습니다.</p><p>YACReader는 단독 응용 프로그램으로 컴퓨터의 만화를 보는 데도 쓸 수 있습니다.</p> @@ -2276,11 +3200,11 @@ Missing files: %3 Close to tray - 트레이로 최소화 + 닫기 시 트레이로 이동 - Start into the system tray + Start in the system tray 시스템 트레이에서 시작 @@ -2391,10 +3315,10 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2435,7 +3359,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Restore defautls + Restore defaults 기본값으로 복원 @@ -2476,7 +3400,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Comic Flow - 코믹 플로우 + Comic Flow @@ -2513,12 +3437,93 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFiles - + + + Unknown Series + 알 수 없는 시리즈 + + + + series + 시리즈 + + + + Unknown Publisher + 알 수 없는 출판사 + + + + publisher + 출판사 + + + + title + 제목 + + + + Unknown Comic + 알 수 없는 만화 + + + + Series #Number - Title + 시리즈 #번호 - 제목 + + + + Series #Number + 시리즈 #번호 + + + + Series #Number (of Count) + 시리즈 #번호 (전체 개수 중) + + + + Number - Title + 번호 - 제목 + + + + Series #Number (Year) + 시리즈 #번호 (연도) + + + + Publisher / Series / Series #Number - Title + 출판사 / 시리즈 / 시리즈 #번호 - 제목 + + + + Publisher / Imprint / Series / Series #Number - Title + 출판사 / 임프린트 / 시리즈 / 시리즈 #번호 - 제목 + + + + Series / Series #Number + 시리즈 / 시리즈 #번호 + + + + Publisher / Series / Series #Number (Year) + 출판사 / 시리즈 / 시리즈 #번호 (연도) + + + + Series / original file name + 시리즈 / 원래 파일 이름 + + + Renamed, %1 is already in use 이름 변경됨, %1은(는) 이미 사용 중입니다 - + Missing metadata: %1 누락된 메타데이터: %1 @@ -2527,62 +3532,82 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %1 could not be created %1을(를) 만들 수 없습니다 + + + %1 (copy also failed: %2) + %1 (복사도 실패함: %2) + + + + the copy did not have the same size as the original + 복사본의 크기가 원본과 다름 + + + + the original could not be deleted after it was copied + 복사 후 원본을 삭제할 수 없음 + + + + The destination folder could not be created. + 대상 폴더를 만들지 못했습니다. + + + + %1 (the file was left at %2) + %1 (파일이 %2에 남아 있음) + OrganizeFilesCoordinator - - + + Organize files 파일 정리 - + This folder does not contain any comics. 이 폴더에는 만화가 없습니다. - - This library is busy: %1 - 이 라이브러리는 사용 중입니다: %1 - - - + the library database could not be opened 라이브러리 데이터베이스를 열 수 없습니다 - + the library database could not be locked for writing 쓰기용으로 라이브러리 데이터베이스를 잠글 수 없습니다 - + a folder entry could not be restored 폴더 항목을 복원할 수 없습니다 - + a comic entry could not be updated 만화 항목을 업데이트할 수 없습니다 - + the library database could not be saved: %1 라이브러리 데이터베이스를 저장할 수 없습니다: %1 - + the record of the last organize run could not be read 마지막 정리 작업의 기록을 읽을 수 없습니다 - + the folder %1 could not be created %1 폴더를 만들 수 없습니다 - + %n file(s) could not be moved back %n개 파일을 되돌리지 못했습니다 @@ -2591,10 +3616,6 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFilesDialog - - Format: - 형식: - Organize files @@ -2644,7 +3665,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Optional part < > - 선택 부분 < > + 선택적 부분 < > @@ -2726,19 +3747,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Cancel 취소 - - Copy the list - 목록 복사 - Undo 실행 취소 - - Close - 닫기 - Copy failure details @@ -3095,7 +4108,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Optional parts - 선택 부분 + 선택적 부분 @@ -3142,274 +4155,383 @@ To stop an automatic update tap on the loading indicator next to the Libraries t PropertiesDialog - + General info 일반 정보 - + Authors 작가진 - + Publishing 출판 - + Plot 줄거리 - + Notes 메모 - + Cover page 표지 페이지 - + Load previous page as cover 이전 페이지를 표지로 불러오기 - + Load next page as cover 다음 페이지를 표지로 불러오기 - + Reset cover to the default image 표지를 기본 이미지로 초기화 - + Load custom cover image 사용자 지정 표지 이미지 불러오기 - + Series: 시리즈: - + Title: 제목: - - + + e.g. 1-5, 8, 0.5, Annual 1 + 예: 1-5, 8, 0.5, Annual 1 + + + + of: - / + / - + + Issue number: - 이슈 번호: + 호 번호: - + Volume: 볼륨: - + Arc number: 아크 번호: - + Story arc: 스토리 아크: - + alt. number: 대체 번호: - + Alternate series: 대체 시리즈: - + Series Group: 시리즈 그룹: - + Genre: 장르: - + Size: 크기: - + Writer(s): 글: - + Penciller(s): 밑그림: - + Inker(s): 펜선: - + Colorist(s): 채색: - + Letterer(s): 식자: - + Cover Artist(s): 표지 그림: - + Editor(s): 편집: - + Imprint: - 임프린트: + 출판 브랜드: - + Day: 일: - + Month: 월: - + Year: 연도: - + Publisher: 출판사: - + Format: 형식: - + Color/BW: 컬러/흑백: - + Age rating: 연령 등급: - + Type: 유형: - + Language (ISO): 언어 (ISO): - + Synopsis: 줄거리: - + Characters: 등장인물: - + Teams: 팀: - + Locations: 장소: - + Main character or team: 주요 등장인물 또는 팀: - + Review: 리뷰: - + Notes: 메모: - + Tags: 태그: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> Comic Vine 링크: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 보기 </a> - + Not found 찾을 수 없음 - + Comic not found. You should update your library. 만화를 찾을 수 없습니다. 라이브러리를 업데이트하세요. - + Edit selected comics information 선택한 만화 정보 편집 - + + + Track missing issues in "%1" + "%1"에서 누락된 호 추적 + + + + Issues: + 호: + + + + Track + 추적 + + + + At most %1 issues can be tracked at once. + 한 번에 최대 %1개의 호를 추적할 수 있습니다. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + 호 번호를 확인하세요. 범위는 오름차순이어야 하며 쉼표 사이의 항목은 비워 둘 수 없습니다. + + + + No new issues to track. + 추적할 새 호가 없습니다. + + + + %n issue(s) will be tracked: %1. + + %n개의 호를 추적합니다: %1. + + + + + That is a lot of issues; check the numbers. + 호가 많습니다. 번호를 확인하세요. + + + + %n issue(s) already in the folder. + + 이미 폴더에 있는 호 %n개. + + + + + %n repeated issue(s). + + 중복된 호 %n개. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + 추적되지 않은 호 + + + + The missing issues could not be tracked. + 누락된 호를 추적하지 못했습니다. + + + + Comic skipped + 만화 건너뜀 + + + + A comic was removed from the library or now has a different file, so it is skipped. + 만화가 라이브러리에서 제거되었거나 파일이 달라져 건너뜁니다. + + + + + Changes not saved + 변경 사항이 저장되지 않음 + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + 편집 중인 만화가 라이브러리에서 제거되었거나 파일이 달라졌습니다. 변경 사항이 저장되지 않았습니다. + + + + The changes could not be saved. + 변경 사항을 저장하지 못했습니다. + + + Invalid cover 잘못된 표지 - + The image is invalid. 이미지가 유효하지 않습니다. - + Edit comic information 만화 정보 편집 @@ -3427,12 +4549,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ./utils에서 7z 라이브러리를 불러올 수 없습니다 - + Select custom cover 사용자 지정 표지 선택 - + Images (%1) 이미지 (%1) @@ -3465,31 +4587,164 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists 새 읽기 목록 추가 - - + + List name: 목록 이름: - + + Import reading lists + 읽기 목록 가져오기 + + + + Reading lists (*.cbl *.json);;All files (*) + 읽기 목록 (*.cbl *.json);;모든 파일 (*) + + + + New smart list + 새 스마트 목록 + + + + + The smart list could not be saved. + 스마트 목록을 저장하지 못했습니다. + + + + Edit smart list + 스마트 목록 편집 + + + + Delete folder + 폴더 삭제 + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + "%1" 폴더와 그 안의 모든 항목을 삭제할까요? 하위 폴더와 목록은 삭제되지만 만화 파일은 유지됩니다. + + + Delete list/label 목록/라벨 삭제 - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? 선택한 항목이 삭제됩니다. 디스크에서 만화나 폴더는 삭제되지 않습니다. 계속하시겠습니까? - + Rename list name 목록 이름 변경 + + + Rename folder + 폴더 이름 바꾸기 + + + + + Folder name: + 폴더 이름: + + + + + + New folder + 새 폴더 + + + + + The folder could not be created. + 폴더를 만들지 못했습니다. + + + + Reading list + 읽기 목록 + + + + CBL reading list (*.cbl) + CBL 읽기 목록 (*.cbl) + + + + JSON reading list (*.json) + JSON 읽기 목록 (*.json) + + + + Web page (*.html) + 웹 페이지 (*.html) + + + + + Export reading list + 읽기 목록 내보내기 + + + + Exporting reading list... + 읽기 목록 내보내는 중... + + + + The file could not be written. + 파일을 기록하지 못했습니다. + + + + Move to folder + 폴더로 이동 + + + + "%1" could not be moved. + "%1"을(를) 이동하지 못했습니다. + + + + ReadingListPage + + + %n comic(s) + + 만화 %n개 + + + + + %n missing + + %n개 없음 + + + + + Missing + 누락 + + + + Exported from YACReader on %1 + %1에 YACReader에서 내보냄 + RenameLibraryDialog @@ -3510,7 +4765,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - New Library Name : + New Library Name: 새 라이브러리 이름: @@ -3518,8 +4773,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ScraperResultsPaginator - Number of volumes found : %1 - 검색된 볼륨 수 : %1 + Number of volumes found: %1 + 검색된 볼륨 수: %1 @@ -3529,8 +4784,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of %1 found : %2 - 검색된 %1 수 : %2 + Number of %1 found: %2 + 검색된 %1 수: %2 @@ -3578,7 +4833,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Volume identifier - 권 식별자 + 볼륨 식별자 @@ -3603,7 +4858,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Penciller credit - 연필화 작가 크레딧 + 밑그림 작가 크레딧 @@ -3618,7 +4873,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Letterer credit - 레터링 작가 크레딧 + 식자 작가 크레딧 @@ -3648,7 +4903,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Characters appearing in the comic - 만화에 등장하는 캐릭터 + 만화에 등장하는 등장인물 @@ -3663,7 +4918,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Primary character or team - 주요 캐릭터 또는 팀 + 주요 등장인물 또는 팀 @@ -3892,6 +5147,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. 바로 입력을 시작하세요. 일반 텍스트는 모든 메타데이터에서 검색됩니다. + + + %1 -> search for `%1` in any database field + %1 -> ?? ?????? ???? `%1` ?? + 1. Search everywhere @@ -3987,7 +5247,9 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - 팁: 공백은 AND처럼 작동하며 검색은 대소문자를 구분하지 않습니다. 값에 공백을 포함하려면 따옴표를 사용하세요. + 팁: +공백은 AND처럼 작동하며 검색은 대소문자를 구분하지 않습니다. +값에 공백을 포함하려면 따옴표를 사용하세요. @@ -4142,7 +5404,7 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. + Please select the right comic info. 맞는 만화 정보를 선택하세요. @@ -4170,7 +5432,7 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. 만화에 맞는 시리즈를 선택하세요. @@ -4209,61 +5471,66 @@ Use quotes to include spaces in a value. Unknown - 알 수 없음 + 알 수 없음 %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 알 수 없음) %1 series - + 시리즈 %1개 - + %1 comics selected %n comic(s) selected - 만화 %1개 선택됨 + 만화 %1개 선택됨 - + Read - 읽음 + 읽음 - + In progress - 읽는 중 + 읽는 중 - + Unread - 읽지 않음 + 읽지 않음 - + Total pages - + 전체 페이지 - + Total size - + 전체 크기 + + + + Missing + 누락 - + Series - 시리즈 + 시리즈 SeriesQuestion - You are trying to get information for various comics at once, are they part of the same series? + You are trying to get information for various comics at once. Are they part of the same series? 여러 만화의 정보를 한 번에 가져오려고 합니다. 같은 시리즈에 속하는 만화입니까? @@ -4285,22 +5552,6 @@ Use quotes to include spaces in a value. set port 포트 설정 - - Server connectivity information - 서버 연결 정보 - - - Scan it! - 스캔하세요! - - - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader는 iOS와 Android 기기에서도 사용할 수 있습니다.<br/><a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> 또는 <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>에서 확인하세요. - - - Choose an IP address - IP 주소 선택 - @@ -4352,38 +5603,69 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader는 iOS와 Android에서 사용할 수 있습니다. <a href='https://ios.yacreader.com'>iOS</a> 또는 <a href='https://android.yacreader.com'>Android</a>용 앱을 만나 보세요. + + + SmartListDialog + + + New smart list + 새 스마트 목록 + + + + Edit smart list + 스마트 목록 편집 + + + + Search filters and syntax + 검색 필터 및 구문 + + + + Name: + 이름: + - enable the server - 서버 사용 + + Search: + 검색: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. 왼쪽의 만화 목록을 만화 정보와 일치할 때까지 정렬하세요. - + sort comics to match comic information 만화 정보에 맞게 정렬 - + issues - 이슈 + 호 - + remove selected comics 선택한 만화 제거 - + restore all removed comics 제거한 만화 모두 복원 + + + Add the %n other issue(s) of this volume as missing comics + + 이 권의 다른 %n개 호를 누락된 만화로 추가 + + ThemeEditorDialog @@ -4599,7 +5881,7 @@ Use quotes to include spaces in a value. - Updating.... + Updating... 업데이트 중... @@ -4608,6 +5890,14 @@ Use quotes to include spaces in a value. 라이브러리 업데이트 + + ViewSelector + + + View + + + VolumeComicsModel @@ -4626,7 +5916,7 @@ Use quotes to include spaces in a value. issues - 이슈 + 호 @@ -4699,7 +5989,7 @@ Use quotes to include spaces in a value. Zoom - 확대/축소 + Zoom @@ -4743,8 +6033,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - 수직 동기화 사용 (전체화면 결의 이미지 품질 향상, 성능 저하) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + VSync 사용 (전체 화면 모드에서 이미지 품질 향상, 성능 저하) @@ -4783,11 +6073,49 @@ Use quotes to include spaces in a value. 이전 버전 + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 라이브러리 데이터베이스가 없습니다. XML 정보를 다시 검색하기 전에 백업을 복원하거나 라이브러리를 다시 만드세요. + + + + Unable to load the archive backend: %1 + 압축 파일 백엔드를 불러올 수 없습니다: %1 + + + + Unable to start an XML rescan transaction. + XML 재검색 트랜잭션을 시작할 수 없습니다. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 선택한 폴더가 더 이상 이 라이브러리에 없습니다. 다시 검색하기 전에 라이브러리를 새로 불러오세요. + + + + Unable to read comics for the XML rescan. + XML 재검색을 위한 만화를 읽을 수 없습니다. + + + + Unable to commit XML metadata changes. + XML 메타데이터 변경 사항을 커밋할 수 없습니다. + + + + Unable to update XML metadata for %1. + %1의 XML 메타데이터를 업데이트할 수 없습니다. + + YACReaderFieldEdit - + Click to overwrite 클릭해서 덮어쓰기 @@ -4800,10 +6128,12 @@ Use quotes to include spaces in a value. YACReaderFieldPlainTextEdit + + - - - + + + Click to overwrite 클릭해서 덮어쓰기 diff --git a/YACReaderLibrary/yacreaderlibrary_nl.ts b/YACReaderLibrary/yacreaderlibrary_nl.ts index a4bddd4f9..f6d979544 100644 --- a/YACReaderLibrary/yacreaderlibrary_nl.ts +++ b/YACReaderLibrary/yacreaderlibrary_nl.ts @@ -21,6 +21,66 @@ Label name: Labelnaam: + + + red + Rood + + + + orange + Oranje + + + + yellow + Geel + + + + green + Groen + + + + cyan + Cyaan + + + + blue + Blauw + + + + violet + Violet + + + + purple + Purper + + + + pink + Roze + + + + white + Wit + + + + light + Licht + + + + dark + Donker + Choose a color: @@ -42,7 +102,7 @@ Add an existing library - Voeg een bestaand bibliotheek toe + Een bestaande bibliotheek toevoegen @@ -51,13 +111,13 @@ - Comics folder : - Strips map: + Comics folder: + Stripmap: - Library name : - Bibliotheek Naam : + Library name: + Bibliotheeknaam: @@ -69,8 +129,8 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Voordat je verbinding kunt maken met Comic Vine, heb je een eigen API-sleutel nodig. Vraag er <a href="http://www.comicvine.com/api/">hier</a> één gratis aan + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Voordat u verbinding kunt maken met Comic Vine, hebt u een eigen API-sleutel nodig. Vraag er <a href="http://www.comicvine.com/api/">hier</a> één gratis aan @@ -118,7 +178,7 @@ Remove this user-imported theme - Verwijder dit door de gebruiker geïmporteerde thema + Dit door de gebruiker geïmporteerde thema verwijderen @@ -200,103 +260,625 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Comic Flow verbergen + + Find comic › + Strip zoeken › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Strips kopiëren... + + Series + Serie - Moving comics... - Strips verplaatsen... + + Number + Nummer + + + + Volume + Deel + + + + Year + Jaar + + + + Format + Formaat + + + + + ComicVine ID + Comic Vine-ID + + + + series and number + reeks en nummer + + + + series without the volume, and number + reeks zonder volume en nummer + + + + letters and digits of the series, and number + letters en cijfers van de reeks en nummer + + + + series with its year, and number + reeks met jaartal en nummer + + + + volume %1 + volume %1 + + + + Comic in your library + Strip in uw bibliotheek + + + + Missing in your library + Ontbreekt in uw bibliotheek + + + + Changed by you. + Door u gewijzigd. + + + + The entry has this comic. + Dit item heeft deze strip. + + + + Matched by %1. + Gekoppeld op basis van %1. + + + + Your library already has this comic as missing. Matched by %1. + Deze strip staat al als ontbrekend in uw bibliotheek. Gekoppeld op basis van %1. + + + + Possible comic %1 + Mogelijke strip %1 + + + + + Changed by you: it is imported as missing. + Door u gewijzigd: wordt als ontbrekend geïmporteerd. + + + + The comics have the same ComicVine ID. + De strips hebben dezelfde Comic Vine-ID. + + + + The comic has the same series and number, but its volume or year is different. + De strip heeft dezelfde reeks en hetzelfde nummer, maar een ander volume of jaartal. + + + + The comics have the same series and number. + De strips hebben dezelfde reeks en hetzelfde nummer. + + + + Values that are different from the file are in amber. + Waarden die afwijken van het bestand zijn amberkleurig. + + + + Closest series in your library + Meest overeenkomende reeksen in uw bibliotheek + + + + Library + Bibliotheek + + + + Has %1 + Heeft %1 + + + + No comic found. Tried: %1. + Geen strip gevonden. Geprobeerd: %1. + + + + In the file + In het bestand + + + + Entry %1 of %2 + Item %1 van %2 + + + + Use this comic + Deze strip gebruiken + + + + Use this missing comic + Deze ontbrekende strip gebruiken + + + + CblFindComicPage + + + ‹ All entries + ‹ Alle items + + + + Previous problem + Vorig probleem + + + + Next problem + Volgend probleem + + + + Folders + Mappen + + + + Search the whole library + Hele bibliotheek doorzoeken + + + + File + Bestand + + + + Series + Serie + + + + No. + Nr. + + + + Volume + Deel + + + + Year + Jaar + + + + Go to the next problem after I choose + Na mijn keuze naar het volgende probleem gaan + + + + Use this comic + Deze strip gebruiken + + + + Cancel + Annuleren + + + + Search in this folder + In deze map zoeken + + + + Entry %1 of %2 + Item %1 van %2 + + + + Missing: no comic found. Browse or search the library. + Ontbreekt: geen strip gevonden. Blader of zoek in de bibliotheek. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Onduidelijk: %n mogelijke strip. Blader of zoek in de bibliotheek. + Onduidelijk: %n mogelijke strips. Blader of zoek in de bibliotheek. + + + + + Browse or search the library to choose another comic. + Blader of zoek in de bibliotheek om een andere strip te kiezen. + + + + Problem %1 of %2 + Probleem %1 van %2 + + + + %n problem(s) + + %n probleem + %n problemen + + + + + + + Library + Bibliotheek + + + + Search results + Zoekresultaten + + + + CblImportDialog + + + Match to library comics — %1 + Aan bibliotheekstrips koppelen — %1 + + + + Apply + Toepassen + + + + Close + Sluiten + + + + Skip + Overslaan + + + + Import + Importeren + + + + &Name: + &Naam: + + + + A reading list with this name already exists. Import creates another one. + Er bestaat al een leeslijst met deze naam. Importeren maakt een nieuwe aan. + + + + Filter entries + Items filteren + + + + Matching the books of %1 with your library... + Boeken van %1 aan uw bibliotheek koppelen... + + + + Matching the entries of %1 with your library... + Items van %1 aan uw bibliotheek koppelen... + + + + + Match to library comics + Aan bibliotheekstrips koppelen + + + + This also gives comics to entries in %n other list(s): %1 + + Hiermee krijgen ook items in %n andere lijst strips: %1 + Hiermee krijgen ook items in %n andere lijsten strips: %1 + + + + + The changes could not be applied. Nothing was changed. + De wijzigingen konden niet worden toegepast. Er is niets gewijzigd. + + + + The file cannot be opened. + Het bestand kan niet worden geopend. + + + + The file is damaged (line %1, column %2). + Het bestand is beschadigd (regel %1, kolom %2). + + + + The file is not a reading list. + Het bestand is geen leeslijst. + + + + The library cannot be read. + De bibliotheek kan niet worden gelezen. + + + + Import reading lists + Leeslijsten importeren + + + + The reading list could not be imported. Nothing was written. + De leeslijst kon niet worden geïmporteerd. Er is niets weggeschreven. + + + + Import reading lists — %1 (%2 of %3) + Leeslijsten importeren — %1 (%2 van %3) + + + + Import reading lists — %1 + Leeslijsten importeren — %1 + + + + All (%1) + Alle (%1) + + + + Matched (%1) + Gekoppeld (%1) + + + + Ambiguous (%1) + Onduidelijk (%1) + + + + Missing (%1) + Ontbrekend (%1) + + + + Changed by you (%1) + Door u gewijzigd (%1) + + + + %1 of %n have a comic + + %1 van %n heeft een strip + %1 van %n hebben een strip + + + + + Find comic... + Strip zoeken... + + + + Use the original match + Oorspronkelijke koppeling gebruiken + + + + Make missing + Als ontbrekend markeren + + + + Import as missing + Als ontbrekend importeren + + + + CblImportModel + + + Matched + Gekoppeld + + + + Ambiguous + Onduidelijk + + + + Missing + Ontbrekend + + + + Changed + Gewijzigd + + + + # + # + + + + In the file + In het bestand + + + + Comic in your library + Strip in uw bibliotheek + + + + Status + Status + + + + Already missing in your library + Staat al als ontbrekend in uw bibliotheek + + + + %n possible comic(s) + + %n mogelijke strip + %n mogelijke strips + + + + + Not in library + Niet in bibliotheek + + + + CblMatchComicDialog + + + Match to library comic + Aan bibliotheekstrip koppelen + + + + Loading the library... + Bibliotheek laden... + + + + The library cannot be read. + De bibliotheek kan niet worden gelezen. + + + + ClassicComicsView + + + Hide comic flow + Comic Flow verbergen ComicInfoView - + Characters Personages - + Main character or team Hoofdpersoon of team - + Teams - Ploegen + Teams - + Locations Locaties - + Authors Auteurs - + writer schrijver - + penciller tekenaar - + inker inker - + colorist inkleurder - + letterer letteraar - + cover artist - covertekenaar + omslagtekenaar - + editor redacteur - + imprint - fondslabel + imprint - + Publisher Uitgever - + color kleur - + b/w z/w @@ -304,28 +886,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader niet gevonden - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader niet gevonden. YACReader moet in dezelfde map worden geïnstalleerd als YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader niet gevonden. Er is mogelijk een probleem met uw YACReader-installatie. - + Error Fout - + Error opening comic with third party reader. Fout bij het openen van een strip met een lezer van een derde partij. @@ -333,136 +915,180 @@ ComicModel - + no - neen + nee - + yes - Ja + ja - + Read Gelezen - + Size - Grootte(MB) + Grootte - + Pages Pagina's - + Title Titel - + File Name Bestandsnaam - + Current Page Huidige pagina - + Publication Date Publicatiedatum - + Rating Beoordeling - + Series Serie - + Volume Deel - + Story Arc - Verhaalboog + Verhaallijn ComicVineDialog - + skip overslaan - + back - rug + terug - + next volgende - + search - zoekopdracht + zoeken - + close - dichtbij + sluiten - - - + + + Looking for volume... Op zoek naar volumes... - - + + comic %1 of %2 - %3 strip %1 van %2 - %3 - + %1 comics selected %1 strips geselecteerd - - Error connecting to ComicVine - Fout bij verbinden met ComicVine + + Error connecting to Comic Vine + Fout bij verbinden met Comic Vine - - - Retrieving tags for : %1 + + + Retrieving tags for: %1 Tags ophalen voor: %1 - + + Creating missing comic %1 of %2 + Ontbrekende strip %1 van %2 aanmaken + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine gaf geen antwoord voor %n strip. + Comic Vine gaf geen antwoord voor %n strips. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n ontbrekende strip kreeg alleen de basisgegevens van het volume. + %n ontbrekende strips kregen alleen de basisgegevens van het volume. + + + + + %n missing comic(s) have no cover. + + %n ontbrekende strip heeft geen omslag. + %n ontbrekende strips hebben geen omslag. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine staat ongeveer 200 verzoeken per uur toe; probeer het later opnieuw voor de niet bijgewerkte strips. + + + + Try again later for the comics that were not updated. + Probeer het later opnieuw voor de niet bijgewerkte strips. + + + + Comic Vine + Comic Vine + + + Retrieving volume info... Volume-informatie ophalen... - + Looking for comic... - Op zoek naar komische... + Op zoek naar strip... @@ -478,7 +1104,7 @@ Create new library - Een nieuwe Bibliotheek aanmaken + Een nieuwe bibliotheek aanmaken @@ -492,38 +1118,46 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - Een bibliotheek aanmaken kan enkele minuten duren. U kunt het proces stoppen en de bibliotheek later voltooien. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + Een bibliotheek aanmaken kan enkele minuten duren. U kunt het proces stoppen en de bibliotheek later bijwerken om de taak te voltooien. The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - De geselecteerde pad bestaat niet of is geen geldig pad. Controleer of u schrijftoegang hebt tot deze map + Het geselecteerde pad bestaat niet of is geen geldig pad. Controleer of u schrijftoegang hebt tot deze map - Comics folder : - Strips map: + Comics folder: + Stripmap: - Library Name : - Bibliotheek Naam : + Library Name: + Bibliotheeknaam: Path not found - Pad niet gevonden + Pad niet gevonden DBHelper - + The folder entry could not be found in the library database. De mapvermelding is niet gevonden in de database van de bibliotheek. + + DelayedProgressDialog + + + Cancel + Annuleren + + EditShortcutsDialog @@ -533,22 +1167,22 @@ - To change a shortcut, double click in the key combination and type the new keys. - Om een ​​snelkoppeling te wijzigen, dubbelklikt u op de toetsencombinatie en typt u de nieuwe toetsen. + To change a shortcut, double-click the key combination and type the new keys. + Om een sneltoets te wijzigen, dubbelklikt u op de toetsencombinatie en typt u de nieuwe toetsen. Shortcuts settings - Instellingen voor snelkoppelingen + Instellingen voor sneltoetsen Shortcut in use - Snelkoppeling in gebruik + Sneltoets in gebruik - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function De sneltoets "%1" is al aan een andere functie toegewezen @@ -581,6 +1215,14 @@ Dit label bevat nog geen strips + + EmptyListFolderWidget + + + This folder is empty + Deze map is leeg + + EmptyReadingListWidget @@ -599,7 +1241,7 @@ You are not reading anything yet, come on!! - Je leest nog niets, kom op!! + U leest nog niets, kom op!! @@ -611,13 +1253,13 @@ ExportComicsInfoDialog - Output file : - Uitvoerbestand: + Output file: + Uitvoerbestand: Destination database name - Bestemmingsdatabase naam + Naam van de doeldatabase @@ -637,7 +1279,7 @@ Export comics info - Strip info exporteren + Stripinfo exporteren @@ -664,8 +1306,8 @@ - Output folder : - Uitvoermap : + Output folder: + Uitvoermap: @@ -675,45 +1317,38 @@ Create covers package - Aanmaken omslag pakket + Omslagpakket aanmaken Destination directory - Doeldirectory + Doelmap FileComic - + 7z not found - 7Z Archiefbestand niet gevonden + 7z niet gevonden - + CRC error on page (%1): some of the pages will not be displayed correctly CRC-fout op pagina (%1): sommige pagina's worden niet correct weergegeven - + Unknown error opening the file Onbekende fout bij het openen van het bestand - - + + Format not supported Formaat niet ondersteund - - FolderContentView - - Continue Reading... - Verder lezen... - - FolderInfoView @@ -788,53 +1423,37 @@ GridComicsView - + Show info Toon informatie - Library - Bibliotheek - - - Folder - Map - - - Favorites - Favorieten - - - Recently added - Onlangs toegevoegd - - - + Manga Manga - + Western manga Westerse manga - + Web comic Webcomic - + Yonkoma Yonkoma - + Comic - Grappig + Strip - + Unknown Onbekend @@ -876,26 +1495,26 @@ - Info database location : - Info database locatie : + Info database location: + Locatie van de infodatabase: Import comics info - Strip info Importeren + Stripinfo importeren Comics info file (*.ydb) - Strips info bestand ( * .ydb) + Stripinfobestand (*.ydb) ImportLibraryDialog - Destination folder : - Doelmap: + Destination folder: + Doelmap: @@ -909,18 +1528,18 @@ - Compresed library covers (*.clc) - Compresed omslag- bibliotheek ( * .clc) + Compressed library covers (*.clc) + Gecomprimeerde omslagbibliotheek (*.clc) - Package location : - Arrangement locatie : + Package location: + Pakketlocatie: - Library Name : - Bibliotheek Naam : + Library Name: + Bibliotheeknaam: @@ -942,28 +1561,28 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <P>YACReaderLibrary maak nu een nieuwe bibliotheek. < /p> <p>Een bibliotheek aanmaken kan enkele minuten duren. U kunt het proces stoppen en de bibliotheek later voltooien. < /p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary maakt nu een nieuwe bibliotheek aan.</p><p>Een bibliotheek aanmaken kan enkele minuten duren. U kunt het proces stoppen en de bibliotheek later bijwerken om de taak te voltooien.</p> Some of the comics being added... - Enkele strips zijn toegevoegd ... + Sommige strips worden toegevoegd... Updating the library - Actualisering van de bibliotheek + De bibliotheek bijwerken - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <P>De huidige bibliotheek wordt bijgewerkt. Voor snellere updates, update uw bibliotheken regelmatig. < /p> <p>u kunt het proces stoppen om later bij te werken. < /p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>De huidige bibliotheek wordt bijgewerkt. Werk uw bibliotheken regelmatig bij voor snellere updates.</p><p>U kunt het proces stoppen en deze bibliotheek later verder bijwerken.</p> Upgrading the library - Het upgraden van de bibliotheek + De bibliotheek upgraden @@ -977,13 +1596,43 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>De huidige bibliotheek wordt gescand op oudere XML-metadata-informatie.</p><p>Dit is slechts één keer nodig en alleen als de bibliotheek is ingepakt met YACReaderLibrary 9.8.2 of eerder.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>De huidige bibliotheek wordt gescand op oudere XML-metadata-informatie.</p><p>Dit is slechts één keer nodig en alleen als de bibliotheek is aangemaakt met YACReaderLibrary 9.8.2 of eerder.</p> <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> - <p>De huidige bibliotheek wordt gecontroleerd op ontbrekende covers en onvolledige stripinformatie.</p><p>Dit kan enkele minuten duren. Je kunt het proces stoppen en later opnieuw uitvoeren.</p> + <p>De huidige bibliotheek wordt gecontroleerd op ontbrekende omslagen en onvolledige stripinformatie.</p><p>Dit kan enkele minuten duren. U kunt het proces stoppen en later opnieuw uitvoeren.</p> + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Kan de gegevensmap van de bibliotheek niet maken: %1. Controleer de toegang tot de map en de beschikbare schijfruimte. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + De gegevensmap van de bibliotheek ontbreekt: %1. Herstel deze of maak de bibliotheek opnieuw aan voordat u bijwerkt. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + De bibliotheekdatabase ontbreekt: %1. Herstel een back-up of maak de bibliotheek opnieuw aan voordat u bijwerkt. + + + + + Unable to start a library database transaction: %1 + Kan geen transactie in de bibliotheekdatabase starten: %1 + + + + + Unable to commit the library database: %1 + Kan de transactie in de bibliotheekdatabase niet vastleggen: %1 @@ -1009,142 +1658,226 @@ Aantal gelezen strips + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Deze bibliotheek is bezet. Wacht tot het onderhoud is voltooid. Als het lijkt vast te lopen, sluit dan de toepassing die onder Details staat en probeer het opnieuw. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Een eerdere onderhoudstaak heeft deze bibliotheek mogelijk niet vrijgegeven. Controleer vóór het herstellen van de toegang of geen enkele taak van YACReaderLibrary of YACReaderLibraryServer deze gebruikt, ook niet op een andere computer. + + + + Retry + Opnieuw proberen + + + + Recover and retry + Toegang herstellen en opnieuw proberen + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + De eigenaar van de bibliotheekvergrendeling is gewijzigd. Probeer de taak opnieuw en controleer de nieuwe eigenaar voordat u de toegang herstelt. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Kan de onderhoudsvergrendeling niet verwijderen: %1. Controleer of het bestand niet alleen-lezen is en of u naar de gegevensmap van de bibliotheek kunt schrijven. Probeer het daarna opnieuw. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Een map neemt het pad van het onderhoudsvergrendelingsbestand in: %1. Hernoem of verplaats die map voordat u het opnieuw probeert. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Kan het bibliotheekvergrendelingsbestand niet maken: %1. Controleer de schrijfrechten voor de gegevensmap van de bibliotheek. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + De gegevensmap van de bibliotheek ontbreekt: %1. Herstel de map of maak de bibliotheek opnieuw aan voordat u deze bijwerkt. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + De bibliotheek heeft een onderhoudsvergrendeling waarvan de eigenaar niet kon worden geverifieerd (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Een andere onderhoudstaak gebruikt deze bibliotheek (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Kan het bibliotheekvergrendelingsbestand niet maken: %1. Controleer de beschikbare schijfruimte en de toegang tot de gegevensmap van de bibliotheek. + + LibraryManagementCoordinator - + Error opening the library - Fout bij openen Bibliotheek + Fout bij het openen van de bibliotheek - + Error creating the library - Fout bij aanmaken Bibliotheek + Fout bij het aanmaken van de bibliotheek - + Error updating the library - Fout bij bijwerken Bibliotheek + Fout bij het bijwerken van de bibliotheek + + + + + XML information rescan + XML-informatie opnieuw scannen + + + + Restore recovery + Herstel van terugzetactie + + + + Library upgrade + Bibliotheek upgraden + + + + Library maintenance + Bibliotheekonderhoud + + + + Recover library access + Toegang tot bibliotheek herstellen LibraryWindow - + The selected folder doesn't contain any library. De geselecteerde map bevat geen bibliotheek. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - Deze bibliotheek is gemaakt met een vorige versie van YACReaderLibrary. Het moet worden bijgewerkt. Nu bijwerken? - - - Remove and delete metadata - Verwijder metagegevens + Deze bibliotheek is gemaakt met een vorige versie van YACReaderLibrary. Zij moet worden bijgewerkt. Nu bijwerken? - + Old library - Oude Bibliotheek + Oude bibliotheek - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - Deze bibliotheek is gemaakt met een nieuwere versie van YACReaderLibrary. Download de nieuwe versie? + Deze bibliotheek is gemaakt met een nieuwere versie van YACReaderLibrary. Wilt u de nieuwe versie nu downloaden? - + Library '%1' is no longer available. Do you want to remove it? - Bibliotheek ' %1' is niet langer beschikbaar. Wilt u het verwijderen? + Bibliotheek '%1' is niet langer beschikbaar. Wilt u deze verwijderen? - - Do you want remove - Wilt u verwijderen + + Do you want to remove the library '%1'? + Wilt u de bibliotheek '%1' verwijderen? - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - Bibliotheek ' %1' is gemaakt met een oudere versie van YACReaderLibrary. Zij moet opnieuw worden aangemaakt. Wilt u de bibliotheek nu aanmaken? + Bibliotheek '%1' is gemaakt met een oudere versie van YACReaderLibrary. Zij moet opnieuw worden aangemaakt. Wilt u de bibliotheek nu aanmaken? - + Library not available - Bibliotheek niet beschikbaar + Bibliotheek niet beschikbaar - + YACReader Library YACReader Bibliotheek - + Update needed Bijwerken is nodig - + Library name already exists - Bibliotheek naam bestaat al + Bibliotheeknaam bestaat al - + There is another library with the name '%1'. - Er is al een bibliotheek met de naam ' %1 '. + Er is al een bibliotheek met de naam '%1'. - + Download new version - Nieuwe versie ophalen + Nieuwe versie downloaden - + Delete comics Strips verwijderen - + All the selected comics will be deleted from your disk. Are you sure? Alle geselecteerde strips worden verwijderd van uw schijf. Weet u het zeker? - + Library not found Bibliotheek niet gevonden - - library? - Bibliotheek? - - - + Are you sure? Weet u het zeker? + Delete folder Map verwijderen - + Upgrade failed Upgrade mislukt - + There were errors during library upgrade in: Er zijn fouten opgetreden tijdens de bibliotheekupgrade in: - + Copying comics... Strips kopiëren... - + Moving comics... Strips verplaatsen... @@ -1164,7 +1897,7 @@ - Please, select a folder first + Please select a folder first Selecteer eerst een map @@ -1180,92 +1913,121 @@ The selected folder and all its contents will be deleted from your disk. Are you sure? - De geselecteerde map en de volledige inhoud ervan worden van uw schijf verwijderd. Weet je het zeker? + De geselecteerde map en de volledige inhoud ervan worden van uw schijf verwijderd. Weet u het zeker? - + Unable to delete Kan niet verwijderen - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. Er is een probleem opgetreden bij het verwijderen van de geselecteerde mappen. Controleer de schrijfrechten en zorg ervoor dat geen toepassingen deze mappen of bestanden daarin gebruiken. - + + Edit smart list... + Slimme lijst bewerken... + + + + Delete smart list + Slimme lijst verwijderen + + + + + + New folder... + Nieuwe map... + + + + + + Move to folder... + Naar map verplaatsen... + + + + Rename folder... + Map hernoemen... + + + + Match to library comics... + Aan bibliotheekstrips koppelen... + + + + Track missing issues... + Ontbrekende nummers bijhouden... + + + + Export reading list... + Leeslijst exporteren... + + + Rename or organize files Bestanden hernoemen of ordenen - + Set the type of the selected comics Het type van de geselecteerde strips instellen - + + Save as smart list… + Opslaan als slimme lijst… + + + Search filters Zoekfilters - + Unread Ongelezen - + In progress Bezig - + Highly rated Hoog gewaardeerd - + Recently added Onlangs toegevoegd - + Search syntax… Zoeksyntaxis… - - A repair of this library is already running (%1). Wait for it to finish. - Er wordt al een herstel van deze bibliotheek uitgevoerd (%1). Wacht tot dit is voltooid. - - - - The library is locked by a repair that did not finish. - De bibliotheek is vergrendeld door een herstel dat niet is voltooid. - - - - The library is locked by a repair started by %1. - De bibliotheek is vergrendeld door een herstel gestart door %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Als u zeker weet dat er geen ander herstel bezig is, kan de vergrendeling worden verwijderd. Vergrendeling verwijderen en doorgaan? - - - + Package operation failed Pakketbewerking mislukt - + The covers package operation could not be completed. De bewerking van het omslagpakket kon niet worden voltooid. - + Restore recovery failed Herstel na onderbroken terugzetting mislukt @@ -1316,144 +2078,144 @@ Map: %1 De database van de bibliotheek kon niet worden bijgewerkt en het hernoemen van de map op de schijf kon niet worden teruggedraaid. De bibliotheek moet nu handmatig worden bijgewerkt. - + Save covers - Bewaar hoesjes + Omslagen opslaan - + You are adding too many libraries. U voegt te veel bibliotheken toe. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. U voegt te veel bibliotheken toe. -Je hebt waarschijnlijk maar één bibliotheek nodig in je stripmap op het hoogste niveau. Je kunt door alle submappen bladeren met behulp van het mappengedeelte in de linkerzijbalk. +U hebt waarschijnlijk maar één bibliotheek nodig in uw stripmap op het hoogste niveau. U kunt door alle submappen bladeren met behulp van het mappengedeelte in de linkerzijbalk. -YACReaderLibrary zal u er niet van weerhouden om meer bibliotheken te creëren, maar u moet het aantal bibliotheken laag houden. +YACReaderLibrary zal u er niet van weerhouden om meer bibliotheken aan te maken, maar u moet het aantal bibliotheken laag houden. - - + + YACReader library database (*.ydb) YACReader-bibliotheekdatabase (*.ydb) - + The library database backup was created at: %1 De back-up van de bibliotheekdatabase is gemaakt in: %1 - + Unable to create the library database backup: %1 De back-up van de bibliotheekdatabase kon niet worden gemaakt: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? - Sluit YACReaderLibraryServer en alle andere YACReader-programma's die deze bibliotheek gebruiken voordat je deze herstelt. Doorgaan? + Sluit YACReaderLibraryServer en alle andere YACReader-programma's die deze bibliotheek gebruiken voordat u deze herstelt. Doorgaan? - + Restoring library database... Bibliotheekdatabase wordt hersteld... - + The current library database is invalid. Restore the selected backup anyway? De huidige bibliotheekdatabase is ongeldig. De geselecteerde back-up toch herstellen? - - - The library maintenance lock may be stale. Remove it and retry? - Het onderhoudsslot van de bibliotheek is mogelijk verouderd. Verwijderen en opnieuw proberen? - - - + Restart YACReaderLibrary before attempting recovery again. -Start YACReaderLibrary opnieuw voordat je nogmaals herstel probeert. +Start YACReaderLibrary opnieuw voordat u nogmaals herstel probeert. - + The library database was restored successfully. Update the library now? De bibliotheekdatabase is hersteld. De bibliotheek nu bijwerken? - + Library database damaged Bibliotheekdatabase beschadigd - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. De database van bibliotheek '%1' is beschadigd. Normale updates, onderhoud en back-ups zijn daarom niet beschikbaar. YACReader kan proberen de database te herstellen. Sommige beschadigde gegevens kunnen mogelijk niet worden hersteld. Bestaande back-ups worden niet gewijzigd. - + Attempt repair Herstel proberen - + Restore a backup... Een back-up herstellen... - + Repairing library database... - Bibliotheekdatabase wordt hersteld... + Bibliotheekdatabase wordt gerepareerd... - - + + + Library database repair Bibliotheekdatabase herstellen - - Another maintenance operation is currently using this library. Try again after it finishes. - Een andere onderhoudsbewerking gebruikt deze bibliotheek momenteel. Probeer het opnieuw wanneer die is voltooid. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + De database van bibliotheek '%1' controleren en indien nodig proberen te repareren? Bestaande back-ups worden niet gewijzigd. - + + Check and repair + Controleren en repareren + + + The library database is already valid. De bibliotheekdatabase is al geldig. - + Library database repaired Bibliotheekdatabase hersteld - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 De bibliotheekdatabase is hersteld door de indexen opnieuw op te bouwen. Het beschadigde origineel is bewaard in: %1 - + Library database rebuilt Bibliotheekdatabase opnieuw opgebouwd - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1464,7 +2226,7 @@ Update the library now? De bibliotheek nu bijwerken? - + The damaged original was preserved at: @@ -1475,82 +2237,123 @@ Het beschadigde origineel is bewaard in: %1 - + Library database repair failed Herstel van bibliotheekdatabase mislukt - + The library database could not be repaired: %1%2 You can restore a backup from the Library menu or recreate the library. - De bibliotheekdatabase kon niet worden hersteld: + De bibliotheekdatabase kon niet worden gerepareerd: %1%2 -Je kunt een back-up herstellen via het menu Bibliotheek of de bibliotheek opnieuw maken. +U kunt een back-up herstellen via het menu Bibliotheek of de bibliotheek opnieuw maken. - + Remove and delete metadata and backups Metagegevens en back-ups verwijderen en wissen - + Library info Bibliotheekinformatie - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. Er is een probleem opgetreden bij het verwijderen van de geselecteerde strips. Controleer of er schrijfrechten zijn voor de geselecteerde bestanden of de map waarin deze zich bevinden. - + Assign comics numbers - Wijs stripnummers toe + Stripnummers toewijzen - - Assign numbers starting in: + + Assign numbers starting at: Nummers toewijzen beginnend met: - + Invalid image Ongeldige afbeelding - + The selected file is not a valid image. Het geselecteerde bestand is geen geldige afbeelding. - + + Error saving cover - Fout bij opslaan van dekking + Fout bij opslaan van omslag - + + There was an error saving the cover image. Er is een fout opgetreden bij het opslaan van de omslagafbeelding. - + + + Match to library comic + Aan bibliotheekstrip koppelen + + + + This also gives the comic to entries in %n other list(s): %1 + + Hiermee krijgen ook items in %n andere lijst deze strip: %1 + Hiermee krijgen ook items in %n andere lijsten deze strip: %1 + + + + + The comic could not be matched. Nothing was changed. + De strip kon niet worden gekoppeld. Er is niets gewijzigd. + + + + The selected missing comics will be removed from this folder. Are you sure? + De geselecteerde ontbrekende strips worden uit deze map verwijderd. Weet u het zeker? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + De geselecteerde strips worden van uw schijf verwijderd en de geselecteerde ontbrekende strips uit deze map. Weet u het zeker? + + + + Remove the comics from Reading? Their reading progress will be reset. + De strips uit Bezig met lezen verwijderen? Hun leesvoortgang wordt gewist. + + + + The comics will be removed from Favorites. Are you sure? + De strips worden uit Favorieten verwijderd. Weet u het zeker? + + + Remove comics - Verwijder strips + Strips verwijderen - + Comics will only be deleted from the current label/list. Are you sure? - Strips worden alleen verwijderd van het huidige label/de huidige lijst. Weet je het zeker? + Strips worden alleen verwijderd van het huidige label/de huidige lijst. Weet u het zeker? - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1564,44 +2367,44 @@ Ontbrekende bestanden: %3 Create a new library - Maak een nieuwe Bibliotheek + Een nieuwe bibliotheek aanmaken Open an existing library - Open een bestaande Bibliotheek + Een bestaande bibliotheek openen Export comics info - Strip info exporteren + Stripinfo exporteren Import comics info - Strip info Importeren + Stripinfo importeren Pack covers - Inpakken strip voorbladen + Omslagen inpakken Pack the covers of the selected library - Inpakken alle strip voorbladen van de geselecteerde Bibliotheek + De omslagen van de geselecteerde bibliotheek inpakken Unpack covers - Uitpakken voorbladen + Omslagen uitpakken Unpack a catalog - Uitpaken van een catalogus + Een catalogus uitpakken @@ -1611,7 +2414,7 @@ Ontbrekende bestanden: %3 Update current library - Huidige Bibliotheek bijwerken + Huidige bibliotheek bijwerken @@ -1635,433 +2438,504 @@ Ontbrekende bestanden: %3 + Repair library database... + Bibliotheekdatabase repareren... + + + + Check the current library database and attempt to repair any damage + Huidige bibliotheekdatabase controleren en eventuele schade proberen te repareren + + + + Recover library access... + Toegang tot bibliotheek herstellen... + + + + Recover access after an interrupted library maintenance operation + Toegang herstellen na een onderbroken onderhoudstaak van de bibliotheek + + + Repair covers and comic info - Covers en stripinformatie herstellen + Omslagen en stripinformatie repareren - + Retry comics with missing covers or incomplete information - Strips met ontbrekende covers of onvolledige informatie opnieuw verwerken + Strips met ontbrekende omslagen of onvolledige informatie opnieuw verwerken - + Rename library Bibliotheek hernoemen - + Rename current library - Huidige Bibliotheek hernoemen + Huidige bibliotheek hernoemen - + Remove library Bibliotheek verwijderen - + Remove current library from your collection - De huidige Bibliotheek verwijderen uit uw verzameling + Huidige bibliotheek verwijderen uit uw verzameling - + Rescan library for XML info Bibliotheek opnieuw scannen op XML-info - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Probeert XML-informatie te vinden die is ingebed in stripbestanden. U hoeft dit alleen te doen als de bibliotheek is gemaakt met versie 9.8.2 of eerdere versies of als u software van derden gebruikt om XML-informatie in de bestanden in te sluiten. - + Open library folder... Bibliotheekmap openen... - + Open the root folder of the current library De hoofdmap van de huidige bibliotheek openen - + Show library info Bibliotheekinfo tonen - + Show information about the current library Toon informatie over de huidige bibliotheek - + Open current comic Huidige strip openen - - Open current comic on YACReader + + Open current comic in YACReader Huidige strip openen in YACReader - + Save selected covers to... Geselecteerde omslagen opslaan in... - + Save covers of the selected comics as JPG files - Sla covers van de geselecteerde strips op als JPG-bestanden + Omslagen van de geselecteerde strips opslaan als JPG-bestanden - - + + Set as read - Instellen als gelezen + Als gelezen markeren - + Set comic as read - Strip Instellen als gelezen + Strip als gelezen markeren - - + + Set as unread - Instellen als ongelezen + Als ongelezen markeren - + Set comic as unread - Strip Instellen als ongelezen + Strip als ongelezen markeren - - + + manga Manga - + Set issue as manga - Stel het probleem in als manga + Uitgave instellen als manga - - + + comic - grappig + strip - + Set issue as normal - Stel het probleem in als normaal + Uitgave instellen als normaal - + western manga westerse manga - + Set issue as western manga - Stel het probleem in als westerse manga + Uitgave instellen als westerse manga - - + + web comic - web-strip + webstrip - + Set issue as web comic - Stel het probleem in als webstrip + Uitgave instellen als webstrip - - + + yonkoma - yokoma + yonkoma - + Set issue as yonkoma - Stel het probleem in als yonkoma + Uitgave instellen als yonkoma - + Show/Hide marks Toon/Verberg markeringen - + Show or hide read marks Toon of verberg leesmarkeringen - + Show/Hide recent indicator Recente indicator tonen/verbergen - + Show or hide recent indicator Toon of verberg recente indicator - - + + Fullscreen mode on/off - Volledig scherm modus aan/of + Modus Volledig scherm aan/uit - + Help, About YACReader Help, Over YACReader - + Add new folder Nieuwe map toevoegen - + Add new folder to the current library Voeg een nieuwe map toe aan de huidige bibliotheek - + Rename folder Map hernoemen - + Rename the current folder on disk and in the library De huidige map hernoemen op de schijf en in de bibliotheek - + Delete folder Map verwijderen - + Delete current folder from disk Verwijder de huidige map van schijf - + Select root node - Selecteer de hoofd categorie + Hoofdknooppunt selecteren - + Expand all nodes - Alle categorieën uitklappen + Alle knooppunten uitklappen - + Collapse all nodes - Vouw alle knooppunten samen + Alle knooppunten samenvouwen - + Show options dialog - Toon opties dialoog + Optiesvenster tonen - + Show comics server options dialog - Toon strips-server opties dialoog + Optiesvenster van de stripserver tonen - - + + Change between comics views Wisselen tussen stripweergaven - + + Grid view + Rasterweergave + + + + Flow view + + + + + Info view + + + + Open folder... - Map openen ... + Map openen... - - + + Rename files... Organize files Bestanden hernoemen... - - + + Organize into folders... In mappen ordenen... - + Set as uncompleted - Ingesteld als onvoltooid + Instellen als onvoltooid - + Set as completed Instellen als voltooid - + Set custom cover - Aangepaste omslag instellen + Aangepast omslag instellen - + Delete custom cover - Aangepaste omslag verwijderen + Aangepast omslag verwijderen - + western manga (left to right) westerse manga (van links naar rechts) - + Open containing folder... - Open map ... + Bijbehorende map openen... - Reset comic rating - Stripbeoordeling opnieuw instellen + + New reading list + Nieuwe leeslijst - + Select all comics Selecteer alle strips - + Edit Bewerken - + Assign current order to comics Wijs de huidige volgorde toe aan strips - + Update cover - Strip omslagen bijwerken + Omslag bijwerken - + Delete selected comics Geselecteerde strips verwijderen - + Delete metadata from selected comics - Verwijder metadata uit geselecteerde strips + Metagegevens van geselecteerde strips verwijderen - + Download tags from Comic Vine Tags downloaden van Comic Vine - + Focus search line - Focus zoeklijn + Focus op zoekveld - + Focus comics view - Focus stripweergave + Focus op stripweergave - + Edit shortcuts - Snelkoppelingen bewerken + Sneltoetsen bewerken - + &Quit &Afsluiten - + Update folder Map bijwerken - + Update current folder - Werk de huidige map bij + Huidige map bijwerken - + Scan legacy XML metadata Scan oudere XML-metagegevens - - Add new reading list - Nieuwe leeslijst toevoegen - - - + Add a new reading list to the current library Voeg een nieuwe leeslijst toe aan de huidige bibliotheek - + + New smart list... + Nieuwe slimme lijst... + + + + Add a saved search to the current library + Een opgeslagen zoekopdracht aan de huidige bibliotheek toevoegen + + + + New folder... + Nieuwe map... + + + + Add a folder for reading lists or smart lists + Een map voor leeslijsten of slimme lijsten toevoegen + + + + Import reading lists... + Leeslijsten importeren... + + + + Import CBL reading lists into the current library + CBL-leeslijsten in de huidige bibliotheek importeren + + + + New + Nieuw + + + + Add or import reading lists + Leeslijsten toevoegen of importeren + + + Remove reading list Leeslijst verwijderen - + Remove current reading list from the library Verwijder de huidige leeslijst uit de bibliotheek - + Add new label - Nieuw etiket toevoegen + Nieuw label toevoegen - + Add a new label to this library Voeg een nieuw label toe aan deze bibliotheek - + Rename selected list Hernoem de geselecteerde lijst - + Rename any selected labels or lists Hernoem alle geselecteerde labels of lijsten - + Add to... Toevoegen aan... - + Favorites Favorieten - + Add selected comics to favorites list Voeg geselecteerde strips toe aan de favorietenlijst - - + + Reset rating Beoordeling opnieuw instellen @@ -2071,7 +2945,7 @@ Ontbrekende bestanden: %3 comic - grappig + strip @@ -2086,97 +2960,110 @@ Ontbrekende bestanden: %3 web comic - web-strip + webstrip - 4koma (top to botom) + 4koma (top to bottom) 4koma (van boven naar beneden) - - - + + + Set type Soort instellen - + Library Bibliotheek - + Folder Map - + Comic - Grappig + Strip + + + + Match to library comic... + Aan bibliotheekstrip koppelen... + + + + + + + Track missing issues... + Ontbrekende nummers bijhouden... - + Open folder... - Map openen ... + Map openen... - + Update folder Map bijwerken - + Rename folder Map hernoemen - + Rename files... Bestanden hernoemen... - + Organize into folders... In mappen ordenen... - + Rescan library for XML info Bibliotheek opnieuw scannen op XML-info - + Set as uncompleted - Ingesteld als onvoltooid + Instellen als onvoltooid - + Set as completed Instellen als voltooid - + Set as read - Instellen als gelezen + Als gelezen markeren - - + + Set as unread - Instellen als ongelezen + Als ongelezen markeren - + Set custom cover - Aangepaste omslag instellen + Aangepast omslag instellen - - + + Delete custom cover - Aangepaste omslag verwijderen + Aangepast omslag verwijderen @@ -2215,11 +3102,57 @@ Ontbrekende bestanden: %3 LocalComicListModel - + file name bestandsnaam + + MoveToFolderDialog + + + Move to folder + Naar map verplaatsen + + + + Top level + Hoofdniveau + + + + Move "%1" to: + "%1" verplaatsen naar: + + + + NewListFolderDialog + + + New folder + Nieuwe map + + + + Reading lists + Leeslijsten + + + + Smart lists + Slimme lijsten + + + + Folder name: + Mapnaam: + + + + For: + Voor: + + NoLibrariesWidget @@ -2230,12 +3163,12 @@ Ontbrekende bestanden: %3 You don't have any libraries yet - Je hebt geen nog libraries + U hebt nog geen bibliotheken - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <P>u kunt een bibliotheek maken in een willekeurige map, YACReaderLibrary importeert alle strips en mappen uit deze map. Alle bibliotheek aangemaakt in het verleden kan je openen. < /p> <p>vergeet niet dat u YACReader kan gebruiken als stand-alone applicatie voor het lezen van de strips op de computer. < /p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>U kunt een bibliotheek maken in een willekeurige map, YACReaderLibrary importeert alle strips en mappen uit deze map. Als u in het verleden bibliotheken hebt aangemaakt, kunt u deze openen.</p><p>Vergeet niet dat u YACReader kunt gebruiken als zelfstandige applicatie voor het lezen van strips op uw computer.</p> @@ -2256,7 +3189,7 @@ Ontbrekende bestanden: %3 Appearance - Verschijning + Uiterlijk @@ -2281,17 +3214,17 @@ Ontbrekende bestanden: %3 Tray icon settings (experimental) - Instellingen voor ladepictogram (experimenteel) + Instellingen voor systeemvakpictogram (experimenteel) Close to tray - Dicht bij lade + Sluiten naar systeemvak - Start into the system tray - Begin in het systeemvak + Start in the system tray + Starten in het systeemvak @@ -2306,7 +3239,7 @@ Ontbrekende bestanden: %3 ComicInfo.xml legacy support - ComicInfo.xml verouderde ondersteuning + Verouderde ondersteuning voor ComicInfo.xml @@ -2317,7 +3250,7 @@ Ontbrekende bestanden: %3 Consider 'recent' items added or updated since X days ago - Overweeg 'recente' items die sinds X dagen geleden zijn toegevoegd of bijgewerkt + Items die in de afgelopen X dagen zijn toegevoegd of bijgewerkt als 'recent' beschouwen @@ -2327,17 +3260,17 @@ Ontbrekende bestanden: %3 Write {comic_file_path} where the path should go in the command - Schrijf {comic_file_path} waar het pad naartoe moet in de opdracht + Schrijf {comic_file_path} op de plaats in de opdracht waar het pad moet komen Clear - Duidelijk + Wissen Update libraries at startup - Update bibliotheken bij het opstarten + Bibliotheken bijwerken bij het opstarten @@ -2347,7 +3280,7 @@ Ontbrekende bestanden: %3 Update libraries periodically - Update bibliotheken regelmatig + Bibliotheken regelmatig bijwerken @@ -2392,7 +3325,7 @@ Ontbrekende bestanden: %3 Update libraries at certain time - Update bibliotheken op een bepaald tijdstip + Bibliotheken op een bepaald tijdstip bijwerken @@ -2401,17 +3334,17 @@ Ontbrekende bestanden: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. WAARSCHUWING! Tijdens bibliotheekupdates is schrijven naar de database uitgeschakeld! Plan geen updates terwijl u de app mogelijk actief gebruikt. -During automatic updates the app will block some of the actions until the update is finished. -Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de titel van Bibliotheken. +Tijdens automatische updates blokkeert de app enkele acties totdat de update is voltooid. +Om een automatische update te stoppen, tikt u op de laadindicator naast de titel van Bibliotheken. @@ -2445,7 +3378,7 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de - Restore defautls + Restore defaults Standaardwaarden herstellen @@ -2513,12 +3446,93 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de OrganizeFiles - + + + Unknown Series + Onbekende reeks + + + + series + reeks + + + + Unknown Publisher + Onbekende uitgever + + + + publisher + uitgever + + + + title + titel + + + + Unknown Comic + Onbekende strip + + + + Series #Number - Title + Reeks #Nummer - Titel + + + + Series #Number + Reeks #Nummer + + + + Series #Number (of Count) + Reeks #Nummer (van Aantal) + + + + Number - Title + Nummer - Titel + + + + Series #Number (Year) + Reeks #Nummer (Jaar) + + + + Publisher / Series / Series #Number - Title + Uitgever / Reeks / Reeks #Nummer - Titel + + + + Publisher / Imprint / Series / Series #Number - Title + Uitgever / Imprint / Reeks / Reeks #Nummer - Titel + + + + Series / Series #Number + Reeks / Reeks #Nummer + + + + Publisher / Series / Series #Number (Year) + Uitgever / Reeks / Reeks #Nummer (Jaar) + + + + Series / original file name + Reeks / oorspronkelijke bestandsnaam + + + Renamed, %1 is already in use Hernoemd, %1 is al in gebruik - + Missing metadata: %1 Ontbrekende metagegevens: %1 @@ -2527,62 +3541,82 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de %1 could not be created %1 kon niet worden gemaakt + + + %1 (copy also failed: %2) + %1 (kopiëren is ook mislukt: %2) + + + + the copy did not have the same size as the original + de kopie had niet dezelfde grootte als het origineel + + + + the original could not be deleted after it was copied + het origineel kon na het kopiëren niet worden verwijderd + + + + The destination folder could not be created. + De doelmap kon niet worden aangemaakt. + + + + %1 (the file was left at %2) + %1 (het bestand is achtergelaten op %2) + OrganizeFilesCoordinator - - + + Organize files Bestanden ordenen - + This folder does not contain any comics. Deze map bevat geen strips. - - This library is busy: %1 - Deze bibliotheek is bezig: %1 - - - + the library database could not be opened de database van de bibliotheek kon niet worden geopend - + the library database could not be locked for writing de database van de bibliotheek kon niet worden vergrendeld om te schrijven - + a folder entry could not be restored een mapvermelding kon niet worden hersteld - + a comic entry could not be updated een stripvermelding kon niet worden bijgewerkt - + the library database could not be saved: %1 de database van de bibliotheek kon niet worden opgeslagen: %1 - + the record of the last organize run could not be read het verslag van de laatste ordening kon niet worden gelezen - + the folder %1 could not be created de map %1 kon niet worden gemaakt - + %n file(s) could not be moved back %n bestand kon niet worden teruggezet @@ -2592,10 +3626,6 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de OrganizeFilesDialog - - Format: - Formaat: - Organize files @@ -2727,19 +3757,11 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de Cancel Annuleren - - Copy the list - De lijst kopiëren - Undo Ongedaan maken - - Close - Sluiten - Copy failure details @@ -3158,274 +4180,386 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de PropertiesDialog - + Day: Dag: - + Plot Verhaal - + Size: - Grootte(MB): + Grootte: - + Year: Jaar: - + Inker(s): - Inkt(en): + Inker(s): - + Publishing - Uitgever + Publicatie - + Publisher: Uitgever: - + General info - Algemene Info + Algemene info - + Color/BW: Kleur/ZW: - + Edit selected comics information - Geselecteerde strip informatie bijwerken + Geselecteerde stripinformatie bewerken - + Penciller(s): Tekenaar(s): - + Colorist(s): Inkleurder(s): - + Genre: - Genretype: + Genre: - + Notes Opmerkingen - + Load previous page as cover Laad de vorige pagina als omslag - + Load next page as cover Laad de volgende pagina als omslag - + Reset cover to the default image - Zet de omslag terug naar de standaardafbeelding + Zet het omslag terug naar de standaardafbeelding - + Load custom cover image Aangepaste omslagafbeelding laden - + Series: Serie: - + + e.g. 1-5, 8, 0.5, Annual 1 + bijv. 1-5, 8, 0.5, Annual 1 + + + + Issue number: - Ids: + Nummer: - + alt. number: alt. nummer: - + Alternate series: Alternatieve serie: - + Series Group: Seriegroep: - + Editor(s): Redacteur(en): - + Imprint: - Opdruk: + Imprint: - + Month: Maand: - + Type: Soort: - + Language (ISO): Taal (ISO): - + Teams: - Ploegen: + Teams: - + Locations: Locaties: - + Main character or team: Hoofdpersoon of team: - + Review: - Beoordeling: + Recensie: - + Notes: Opmerkingen: - + + + Track missing issues in "%1" + Ontbrekende nummers in "%1" bijhouden + + + + Issues: + Nummers: + + + + Track + Bijhouden + + + + At most %1 issues can be tracked at once. + U kunt maximaal %1 nummers tegelijk bijhouden. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Controleer de nummers: een bereik moet oplopen en een deel tussen komma's mag niet leeg zijn. + + + + No new issues to track. + Geen nieuwe nummers om bij te houden. + + + + %n issue(s) will be tracked: %1. + + %n nummer wordt bijgehouden: %1. + %n nummers worden bijgehouden: %1. + + + + + That is a lot of issues; check the numbers. + Dat zijn veel nummers; controleer de nummering. + + + + %n issue(s) already in the folder. + + %n nummer staat al in de map. + %n nummers staan al in de map. + + + + + %n repeated issue(s). + + %n herhaald nummer. + %n herhaalde nummers. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Niet bijgehouden nummers + + + + The missing issues could not be tracked. + De ontbrekende nummers konden niet worden bijgehouden. + + + + Comic skipped + Strip overgeslagen + + + + A comic was removed from the library or now has a different file, so it is skipped. + Een strip is uit de bibliotheek verwijderd of heeft nu een ander bestand en wordt daarom overgeslagen. + + + + + Changes not saved + Wijzigingen niet opgeslagen + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Een strip die u bewerkt is uit de bibliotheek verwijderd of heeft nu een ander bestand. Uw wijzigingen zijn niet opgeslagen. + + + + The changes could not be saved. + De wijzigingen konden niet worden opgeslagen. + + + Invalid cover - Ongeldige dekking + Ongeldig omslag - + The image is invalid. De afbeelding is ongeldig. - + Synopsis: Samenvatting: - + Title: Titel: - + Not found Niet gevonden - + Characters: Personages: - + Authors Auteurs - + Age rating: - Leeftijdsbeperking: + Leeftijdsclassificatie: - + Story arc: Verhaallijn: - + Writer(s): Schrijver(s): - + Tags: - Labels: + Tags: - + Comic not found. You should update your library. - Strip niet gevonden. U moet uw bibliotheek.bijwerken. + Strip niet gevonden. U moet uw bibliotheek bijwerken. - + Edit comic information - Strip informatie bijwerken + Stripinformatie bewerken - + Cover page Omslag - + Cover Artist(s): - Omslag ontwikkelaar(s): + Omslagtekenaar(s): - + Volume: - Inhoud: + Deel: - + Format: Formaat: - + Letterer(s): - Letterzetter(s): + Letteraar/letteraars: - - + + of: van: - + Arc number: - Boognummer: + Verhaallijnnummer: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> Comic Vine-link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> bekijken </a> @@ -3443,12 +4577,12 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de kan 7z lib niet laden vanuit ./utils - + Select custom cover - Selecteer een aangepaste omslag + Selecteer een aangepast omslag - + Images (%1) Afbeeldingen (%1) @@ -3481,30 +4615,165 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de ReadingListManagementCoordinator - + Add new reading lists - Voeg nieuwe leeslijsten toe + Nieuwe leeslijsten toevoegen - - + + List name: Lijstnaam: - + + Import reading lists + Leeslijsten importeren + + + + Reading lists (*.cbl *.json);;All files (*) + Leeslijsten (*.cbl *.json);;Alle bestanden (*) + + + + New smart list + Nieuwe slimme lijst + + + + + The smart list could not be saved. + De slimme lijst kon niet worden opgeslagen. + + + + Edit smart list + Slimme lijst bewerken + + + + Delete folder + Map verwijderen + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Map "%1" en alle inhoud verwijderen? De mappen en lijsten worden verwijderd; uw stripbestanden blijven behouden. + + + Delete list/label Lijst/label verwijderen - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - Het geselecteerde item wordt verwijderd, uw strips of mappen worden NIET van uw schijf verwijderd. Weet je het zeker? + Het geselecteerde item wordt verwijderd, uw strips of mappen worden NIET van uw schijf verwijderd. Weet u het zeker? - + Rename list name - Hernoem de lijstnaam + Lijst hernoemen + + + + Rename folder + Map hernoemen + + + + + Folder name: + Mapnaam: + + + + + + New folder + Nieuwe map + + + + + The folder could not be created. + De map kon niet worden aangemaakt. + + + + Reading list + Leeslijst + + + + CBL reading list (*.cbl) + CBL-leeslijst (*.cbl) + + + + JSON reading list (*.json) + JSON-leeslijst (*.json) + + + + Web page (*.html) + Webpagina (*.html) + + + + + Export reading list + Leeslijst exporteren + + + + Exporting reading list... + Leeslijst exporteren... + + + + The file could not be written. + Het bestand kon niet worden geschreven. + + + + Move to folder + Naar map verplaatsen + + + + "%1" could not be moved. + "%1" kon niet worden verplaatst. + + + + ReadingListPage + + + %n comic(s) + + %n strip + %n strips + + + + + %n missing + + %n ontbreekt + %n ontbreken + + + + + Missing + Ontbrekend + + + + Exported from YACReader on %1 + Geëxporteerd uit YACReader op %1 @@ -3512,7 +4781,7 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de Rename current library - Hernoem de huidige bibiliotheek + Huidige bibliotheek hernoemen @@ -3522,19 +4791,19 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de Rename - Hernoem + Hernoemen - New Library Name : - Nieuwe Bibliotheek Naam : + New Library Name: + Nieuwe bibliotheeknaam: ScraperResultsPaginator - Number of volumes found : %1 + Number of volumes found: %1 Aantal gevonden volumes: %1 @@ -3545,7 +4814,7 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de - Number of %1 found : %2 + Number of %1 found: %2 Aantal %1 gevonden: %2 @@ -3609,7 +4878,7 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de Textual tags - Tekstlabels + Teksttags @@ -3908,6 +5177,11 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de Just start typing. Plain text search across all metadata. Begin gewoon te typen. Platte tekst doorzoekt alle metagegevens. + + + %1 -> search for `%1` in any database field + %1 -> zoek naar `%1` in elk databaseveld + 1. Search everywhere @@ -4003,7 +5277,9 @@ Om een ​​automatische update te stoppen, tikt u op de laadindicator naast de Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Tips: spaties werken als AND en zoekopdrachten zijn niet hoofdlettergevoelig. Gebruik aanhalingstekens om spaties in een waarde op te nemen. + Tips: +Spaties werken als AND en zoekopdrachten zijn niet hoofdlettergevoelig. +Gebruik aanhalingstekens om spaties in een waarde op te nemen. @@ -4098,7 +5374,7 @@ Use quotes to include spaces in a value. Search textual tags - Tekstlabels doorzoeken + Teksttags doorzoeken @@ -4158,7 +5434,7 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. + Please select the right comic info. Selecteer de juiste stripinformatie. @@ -4169,7 +5445,7 @@ Use quotes to include spaces in a value. loading cover - laaddeksel + omslag laden @@ -4179,25 +5455,25 @@ Use quotes to include spaces in a value. comic description unavailable - komische beschrijving niet beschikbaar + stripbeschrijving niet beschikbaar SelectVolume - Please, select the right series for your comic. - Selecteer de juiste serie voor jouw strip. + Please select the right series for your comic. + Selecteer de juiste serie voor uw strip. Filter: - Selectiefilter: + Filter: volumes - delen + volumes @@ -4207,7 +5483,7 @@ Use quotes to include spaces in a value. loading cover - laaddeksel + omslag laden @@ -4225,54 +5501,59 @@ Use quotes to include spaces in a value. Unknown - Onbekend + Onbekend %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 onbekend) %1 series - + %1 series - + %1 comics selected %n comic(s) selected - %1 strips geselecteerd + %1 strips geselecteerd - + Read - Gelezen + Gelezen - + In progress - Bezig + Bezig - + Unread - Ongelezen + Ongelezen - + Total pages - + Totaal aantal pagina's - + Total size - + Totale grootte + + + + Missing + Ontbrekend - + Series - Serie + Serie @@ -4280,17 +5561,17 @@ Use quotes to include spaces in a value. no - neen + nee yes - Ja + ja - You are trying to get information for various comics at once, are they part of the same series? - Je probeert informatie voor verschillende strips tegelijk te krijgen. Maken ze deel uit van dezelfde serie? + You are trying to get information for various comics at once. Are they part of the same series? + U probeert informatie voor verschillende strips tegelijk op te halen. Maken ze deel uit van dezelfde serie? @@ -4309,7 +5590,7 @@ Use quotes to include spaces in a value. Devices on this network can reach your library at the address below. - Apparaten op dit netwerk kunnen je bibliotheek bereiken via het onderstaande adres. + Apparaten op dit netwerk kunnen uw bibliotheek bereiken via het onderstaande adres. @@ -4346,60 +5627,76 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader is beschikbaar voor iOS en Android. Ontdek de app voor <a href='https://ios.yacreader.com'>iOS</a> of <a href='https://android.yacreader.com'>Android</a>. - - enable the server - De server instellen - Set port set port Poort instellen + + + SmartListDialog + + + New smart list + Nieuwe slimme lijst + - Server connectivity information - Informatie over serverconnectiviteit + + Edit smart list + Slimme lijst bewerken - Scan it! - Scan het! + + Search filters and syntax + Zoekfilters en syntaxis - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader is beschikbaar voor iOS- en Android-apparaten.<br/>Ontdek het voor <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> of <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Name: + Naam: - Choose an IP address - Kies een IP-adres + + Search: + Zoekopdracht: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. Sorteer de lijst met strips aan de linkerkant totdat deze overeenkomt met de informatie over de strips. - + sort comics to match comic information sorteer strips zodat ze overeenkomen met stripinformatie - + issues - problemen + uitgaven - + remove selected comics verwijder geselecteerde strips - + restore all removed comics herstel alle verwijderde strips + + + Add the %n other issue(s) of this volume as missing comics + + Het %n andere nummer van dit volume als ontbrekende strip toevoegen + De %n andere nummers van dit volume als ontbrekende strips toevoegen + + ThemeEditorDialog @@ -4421,7 +5718,7 @@ Use quotes to include spaces in a value. i - ? + i @@ -4441,7 +5738,7 @@ Use quotes to include spaces in a value. Search… - Zoekopdracht… + Zoeken… @@ -4456,7 +5753,7 @@ Use quotes to include spaces in a value. ID: - Identiteitskaart: + ID: @@ -4466,7 +5763,7 @@ Use quotes to include spaces in a value. Variant: - Variatie: + Variant: @@ -4476,7 +5773,7 @@ Use quotes to include spaces in a value. Parameter - Instelwaarde + Parameter @@ -4516,7 +5813,7 @@ Use quotes to include spaces in a value. true - WAAR + true @@ -4524,7 +5821,7 @@ Use quotes to include spaces in a value. false - vals + false @@ -4603,7 +5900,7 @@ Use quotes to include spaces in a value. SEARCH - ZOEKOPDRACHT + ZOEKEN @@ -4620,8 +5917,16 @@ Use quotes to include spaces in a value. - Updating.... - Bijwerken.... + Updating... + Bijwerken... + + + + ViewSelector + + + View + @@ -4642,7 +5947,7 @@ Use quotes to include spaces in a value. issues - problemen + uitgaven @@ -4705,7 +6010,7 @@ Use quotes to include spaces in a value. Cover gap - Ruimte tss Omslag + Ruimte tussen omslagen @@ -4715,22 +6020,22 @@ Use quotes to include spaces in a value. Zoom - Vergroting + Zoom Y offset - Y-positie + Y-verschuiving Z offset - Z- positie + Z-verschuiving Cover Angle - Omslag hoek + Omslaghoek @@ -4759,8 +6064,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Gebruik VSync (verbetering van de beeldkwaliteit in de modus volledig scherm, slechtere prestatie) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Gebruik VSync (verbetert de beeldkwaliteit in de modus volledig scherm, maar verlaagt de prestaties) @@ -4799,6 +6104,44 @@ Use quotes to include spaces in a value. Vorige versies + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + De bibliotheekdatabase ontbreekt. Herstel een back-up of maak de bibliotheek opnieuw aan voordat u de XML-informatie opnieuw scant. + + + + Unable to load the archive backend: %1 + Kan de archiefbackend niet laden: %1 + + + + Unable to start an XML rescan transaction. + Kan geen transactie voor het opnieuw scannen van XML starten. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + De geselecteerde map staat niet meer in deze bibliotheek. Herlaad de bibliotheek voordat u opnieuw scant. + + + + Unable to read comics for the XML rescan. + Kan strips voor het opnieuw scannen van XML niet lezen. + + + + Unable to commit XML metadata changes. + Kan wijzigingen in XML-metadata niet vastleggen. + + + + Unable to update XML metadata for %1. + Kan XML-metadata voor %1 niet bijwerken. + + YACReaderFieldEdit @@ -4808,7 +6151,7 @@ Use quotes to include spaces in a value. - + Click to overwrite Klik hier om te overschrijven @@ -4821,10 +6164,12 @@ Use quotes to include spaces in a value. Standaardwaarden herstellen + + - - - + + + Click to overwrite Klik hier om te overschrijven @@ -4834,7 +6179,7 @@ Use quotes to include spaces in a value. Save - Bewaar + Opslaan @@ -4854,12 +6199,12 @@ Use quotes to include spaces in a value. Edit shortcuts - Snelkoppelingen bewerken + Sneltoetsen bewerken Shortcuts - Snelkoppelingen + Sneltoetsen diff --git a/YACReaderLibrary/yacreaderlibrary_pt.ts b/YACReaderLibrary/yacreaderlibrary_pt.ts index 75051cb93..0b6fccf7a 100644 --- a/YACReaderLibrary/yacreaderlibrary_pt.ts +++ b/YACReaderLibrary/yacreaderlibrary_pt.ts @@ -11,10 +11,70 @@ AddLabelDialog + + + red + Vermelho + + + + orange + Laranja + + + + yellow + Amarelo + + + + green + Verde + + + + cyan + Ciano + + + + blue + Azul + + + + violet + Violeta + + + + purple + Púrpura + + + + pink + Rosa + + + + white + Branco + + + + light + Claro + + + + dark + Escuro + Label name: - Nome da etiqueta: + Nome do rótulo: @@ -51,13 +111,13 @@ - Comics folder : - Pasta dos quadrinhos : + Comics folder: + Pasta dos quadrinhos: - Library name : - Nome da Biblioteca : + Library name: + Nome da biblioteca: @@ -69,13 +129,13 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Antes de se conectar ao Comic Vine, você precisa de sua própria chave de API. Por favor, ganhe um <a href="http://www.comicvine.com/api/">aqui</a> grátis + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Antes de se conectar ao Comic Vine, você precisa de sua própria chave de API. Por favor, obtenha uma grátis <a href="http://www.comicvine.com/api/">aqui</a> Paste here your Comic Vine API key - Cole aqui sua chave API do Comic Vine + Cole aqui sua chave de API do Comic Vine @@ -98,7 +158,7 @@ Light - Luz + Claro @@ -118,12 +178,12 @@ Remove this user-imported theme - Remova este tema importado pelo usuário + Remover este tema importado pelo usuário Light: - Luz: + Claro: @@ -153,7 +213,7 @@ Open Theme Editor... - Abra o Editor de Tema... + Abrir editor de tema... @@ -163,7 +223,7 @@ The current theme JSON could not be loaded. - O tema atual JSON não pôde ser carregado. + Não foi possível carregar o JSON do tema atual. @@ -200,103 +260,625 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Ocultar Comic Flow + + Find comic › + Encontrar quadrinho › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Copiando quadrinhos... + + Series + Série - Moving comics... - Quadrinhos em movimento... + + Number + Número + + + + Volume + Volume + + + + Year + Ano + + + + Format + Formato + + + + + ComicVine ID + ID do Comic Vine + + + + series and number + série e número + + + + series without the volume, and number + série sem o volume e número + + + + letters and digits of the series, and number + letras e dígitos da série e número + + + + series with its year, and number + série com o ano e número + + + + volume %1 + volume %1 + + + + Comic in your library + Quadrinho na sua biblioteca + + + + Missing in your library + Ausente na sua biblioteca + + + + Changed by you. + Alterado por você. + + + + The entry has this comic. + A entrada tem este quadrinho. + + + + Matched by %1. + Correspondência por %1. + + + + Your library already has this comic as missing. Matched by %1. + Sua biblioteca já tem este quadrinho como ausente. Correspondência por %1. + + + + Possible comic %1 + Possível quadrinho %1 + + + + + Changed by you: it is imported as missing. + Alterado por você: será importado como ausente. + + + + The comics have the same ComicVine ID. + Os quadrinhos têm o mesmo ID do Comic Vine. + + + + The comic has the same series and number, but its volume or year is different. + O quadrinho tem a mesma série e número, mas o volume ou o ano é diferente. + + + + The comics have the same series and number. + Os quadrinhos têm a mesma série e número. + + + + Values that are different from the file are in amber. + Valores diferentes dos do arquivo aparecem em âmbar. + + + + Closest series in your library + Séries mais próximas na sua biblioteca + + + + Library + Biblioteca + + + + Has %1 + Tem %1 + + + + No comic found. Tried: %1. + Nenhum quadrinho encontrado. Tentativas: %1. + + + + In the file + No arquivo + + + + Entry %1 of %2 + Entrada %1 de %2 + + + + Use this comic + Usar este quadrinho + + + + Use this missing comic + Usar este quadrinho ausente + + + + CblFindComicPage + + + ‹ All entries + ‹ Todas as entradas + + + + Previous problem + Problema anterior + + + + Next problem + Próximo problema + + + + Folders + Pastas + + + + Search the whole library + Pesquisar em toda a biblioteca + + + + File + Arquivo + + + + Series + Série + + + + No. + Nº + + + + Volume + Volume + + + + Year + Ano + + + + Go to the next problem after I choose + Ir para o próximo problema após minha escolha + + + + Use this comic + Usar este quadrinho + + + + Cancel + Cancelar + + + + Search in this folder + Pesquisar nesta pasta + + + + Entry %1 of %2 + Entrada %1 de %2 + + + + Missing: no comic found. Browse or search the library. + Ausente: nenhum quadrinho encontrado. Navegue ou pesquise na biblioteca. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Ambíguo: %n quadrinho possível. Navegue ou pesquise na biblioteca. + Ambíguo: %n quadrinhos possíveis. Navegue ou pesquise na biblioteca. + + + + + Browse or search the library to choose another comic. + Navegue ou pesquise na biblioteca para escolher outro quadrinho. + + + + Problem %1 of %2 + Problema %1 de %2 + + + + %n problem(s) + + %n problema + %n problemas + + + + + + + Library + Biblioteca + + + + Search results + Resultados da pesquisa + + + + CblImportDialog + + + Match to library comics — %1 + Associar aos quadrinhos da biblioteca — %1 + + + + Apply + Aplicar + + + + Close + Fechar + + + + Skip + Ignorar + + + + Import + Importar + + + + &Name: + &Nome: + + + + A reading list with this name already exists. Import creates another one. + Já existe uma lista de leitura com este nome. A importação cria outra. + + + + Filter entries + Filtrar entradas + + + + Matching the books of %1 with your library... + Associando os livros de %1 à sua biblioteca... + + + + Matching the entries of %1 with your library... + Associando as entradas de %1 à sua biblioteca... + + + + + Match to library comics + Associar aos quadrinhos da biblioteca + + + + This also gives comics to entries in %n other list(s): %1 + + Isso também associa quadrinhos a entradas de %n outra lista: %1 + Isso também associa quadrinhos a entradas de %n outras listas: %1 + + + + + The changes could not be applied. Nothing was changed. + Não foi possível aplicar as alterações. Nada foi alterado. + + + + The file cannot be opened. + Não é possível abrir o arquivo. + + + + The file is damaged (line %1, column %2). + O arquivo está danificado (linha %1, coluna %2). + + + + The file is not a reading list. + O arquivo não é uma lista de leitura. + + + + The library cannot be read. + Não é possível ler a biblioteca. + + + + Import reading lists + Importar listas de leitura + + + + The reading list could not be imported. Nothing was written. + Não foi possível importar a lista de leitura. Nada foi gravado. + + + + Import reading lists — %1 (%2 of %3) + Importar listas de leitura — %1 (%2 de %3) + + + + Import reading lists — %1 + Importar listas de leitura — %1 + + + + All (%1) + Todas (%1) + + + + Matched (%1) + Associadas (%1) + + + + Ambiguous (%1) + Ambíguas (%1) + + + + Missing (%1) + Ausentes (%1) + + + + Changed by you (%1) + Alteradas por você (%1) + + + + %1 of %n have a comic + + %1 de %n tem um quadrinho + %1 de %n têm um quadrinho + + + + + Find comic... + Encontrar quadrinho... + + + + Use the original match + Usar a correspondência original + + + + Make missing + Marcar como ausente + + + + Import as missing + Importar como ausente + + + + CblImportModel + + + Matched + Associado + + + + Ambiguous + Ambíguo + + + + Missing + Ausente + + + + Changed + Alterado + + + + # + # + + + + In the file + No arquivo + + + + Comic in your library + Quadrinho na sua biblioteca + + + + Status + Status + + + + Already missing in your library + Já está ausente na sua biblioteca + + + + %n possible comic(s) + + %n quadrinho possível + %n quadrinhos possíveis + + + + + Not in library + Fora da biblioteca + + + + CblMatchComicDialog + + + Match to library comic + Associar a um quadrinho da biblioteca + + + + Loading the library... + Carregando a biblioteca... + + + + The library cannot be read. + Não é possível ler a biblioteca. + + + + ClassicComicsView + + + Hide comic flow + Ocultar Comic Flow ComicInfoView - + Characters Personagens - + Main character or team - Personagem principal ou equipa + Personagem principal ou equipe - + Teams - Equipas + Equipes - + Locations - Localizações + Locais - + Authors Autores - + writer - argumentista + roteirista - + penciller - desenhador (lápis) + desenhista (lápis) - + inker arte-finalista - + colorist colorista - + letterer - letreirista + letrista - + cover artist artista de capa - + editor editor - + imprint - chancela editorial + selo editorial - + Publisher Editora - + color cor - + b/w p/b @@ -304,28 +886,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader não encontrado - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader não encontrado. YACReader deve ser instalado na mesma pasta que YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader não encontrado. Pode haver um problema com a instalação do YACReader. - + Error Erro - + Error opening comic with third party reader. Erro ao abrir o quadrinho com leitor de terceiros. @@ -333,134 +915,178 @@ ComicModel - + yes sim - + no não - + Title Título - + File Name Nome do arquivo - + Pages Páginas - + Size Tamanho - + Read - Ler + Lido - + Current Page Página atual - + Publication Date Data de publicação - + Rating Avaliação - + Series Série - + Volume - Tomo + Volume - + Story Arc - Arco de história + Arco da história ComicVineDialog - + skip pular - + back voltar - + next próximo - + search procurar - + close fechar - - - + + + Looking for volume... Procurando volume... - - + + comic %1 of %2 - %3 - história em quadrinhos %1 de %2 - %3 + quadrinho %1 de %2 - %3 - + %1 comics selected %1 quadrinhos selecionados - - Error connecting to ComicVine - Erro ao conectar-se ao ComicVine + + Error connecting to Comic Vine + Erro ao conectar-se ao Comic Vine - - - Retrieving tags for : %1 + + + Retrieving tags for: %1 Recuperando tags para: %1 - + + Creating missing comic %1 of %2 + Criando quadrinho ausente %1 de %2 + + + + Comic Vine did not answer for %n comic(s). + + O Comic Vine não respondeu para %n quadrinho. + O Comic Vine não respondeu para %n quadrinhos. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n quadrinho ausente recebeu apenas os dados básicos do volume. + %n quadrinhos ausentes receberam apenas os dados básicos do volume. + + + + + %n missing comic(s) have no cover. + + %n quadrinho ausente não tem capa. + %n quadrinhos ausentes não têm capa. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + O Comic Vine permite cerca de 200 solicitações por hora; tente novamente mais tarde para os quadrinhos não atualizados. + + + + Try again later for the comics that were not updated. + Tente novamente mais tarde para os quadrinhos não atualizados. + + + + Comic Vine + Comic Vine + + + Retrieving volume info... Recuperando informações de volume... - + Looking for comic... Procurando quadrinhos... @@ -470,7 +1096,7 @@ Continue Reading... - Continuar a ler... + Continuar lendo... @@ -492,17 +1118,17 @@ - Comics folder : - Pasta dos quadrinhos : + Comics folder: + Pasta dos quadrinhos: - Library Name : - Nome da Biblioteca : + Library Name: + Nome da biblioteca: - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. Criar uma biblioteca pode levar vários minutos. Você pode interromper o processo e atualizar a biblioteca posteriormente para concluir a tarefa. @@ -519,11 +1145,19 @@ DBHelper - + The folder entry could not be found in the library database. A entrada da pasta não foi encontrada no banco de dados da biblioteca. + + DelayedProgressDialog + + + Cancel + Cancelar + + EditShortcutsDialog @@ -533,7 +1167,7 @@ - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. Para alterar um atalho, clique duas vezes na combinação de teclas e digite as novas teclas. @@ -548,7 +1182,7 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function O atalho "%1" já está atribuído a outra função @@ -581,6 +1215,14 @@ Este rótulo ainda não contém quadrinhos + + EmptyListFolderWidget + + + This folder is empty + Esta pasta está vazia + + EmptyReadingListWidget @@ -621,13 +1263,13 @@ - Output file : + Output file: Arquivo de saída: Export comics info - Exportar informa??es dos quadrinhos + Exportar informações dos quadrinhos @@ -659,8 +1301,8 @@ - Output folder : - Pasta de saída : + Output folder: + Pasta de saída: @@ -670,7 +1312,7 @@ Destination directory - Diretório de destino + Pasta de destino @@ -686,34 +1328,27 @@ FileComic - + 7z not found 7z não encontrado - + CRC error on page (%1): some of the pages will not be displayed correctly Erro CRC na página (%1): algumas páginas não serão exibidas corretamente - + Unknown error opening the file Erro desconhecido ao abrir o arquivo - - + + Format not supported Formato não suportado - - FolderContentView - - Continue Reading... - Continuar a ler... - - FolderInfoView @@ -739,7 +1374,7 @@ Read - Ler + Lido @@ -788,53 +1423,37 @@ GridComicsView - - Show info - Mostrar informações - - - Library - Biblioteca - - - Folder - Pasta - - - Favorites - Favoritos - - - Recently added - Adicionados recentemente + + Show info + Mostrar informações - + Manga Mangá - + Western manga Mangá ocidental - + Web comic Quadrinho da web - + Yonkoma Yonkoma - + Comic - Quadrinhos + Quadrinho - + Unknown Desconhecido @@ -872,11 +1491,11 @@ Import comics info - Importar informa??es dos quadrinhos + Importar informações dos quadrinhos - Info database location : + Info database location: Localização do banco de dados de informações: @@ -894,8 +1513,8 @@ ImportLibraryDialog - Destination folder : - Pasta de destino : + Destination folder: + Pasta de destino: @@ -909,18 +1528,18 @@ - Compresed library covers (*.clc) - Capas da biblioteca compactada (*.clc) + Compressed library covers (*.clc) + Capas compactadas da biblioteca (*.clc) - Package location : - Local do pacote : + Package location: + Local do pacote: - Library Name : - Nome da Biblioteca : + Library Name: + Nome da biblioteca: @@ -947,7 +1566,7 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> <p>YACReaderLibrary está criando uma nova biblioteca.</p><p>A criação de uma biblioteca pode levar vários minutos. Você pode interromper o processo e atualizar a biblioteca posteriormente para concluir a tarefa.</p> @@ -957,7 +1576,7 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> <p>A biblioteca atual está sendo atualizada. Para atualizações mais rápidas, atualize suas bibliotecas com frequência.</p><p>Você pode interromper o processo e continuar atualizando esta biblioteca mais tarde.</p> @@ -973,11 +1592,11 @@ Scanning the library - Digitalizando a biblioteca + Verificando a biblioteca - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> <p>A biblioteca atual está sendo verificada em busca de informações de metadados XML legados.</p><p>Isso só será necessário uma vez e somente se a biblioteca tiver sido criada com YACReaderLibrary 9.8.2 ou anterior.</p> @@ -986,6 +1605,36 @@ <p>A biblioteca atual está sendo verificada em busca de capas ausentes e informações incompletas dos quadrinhos.</p><p>Isso pode levar vários minutos. Você pode interromper o processo e executá-lo novamente mais tarde.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Não foi possível criar a pasta de dados da biblioteca: %1. Verifique o acesso à pasta e o espaço disponível em disco. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + A pasta de dados da biblioteca está ausente: %1. Restaure-a ou recrie a biblioteca antes de atualizar. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + O banco de dados da biblioteca está ausente: %1. Restaure um backup ou recrie a biblioteca antes de atualizar. + + + + + Unable to start a library database transaction: %1 + Não foi possível iniciar uma transação no banco de dados da biblioteca: %1 + + + + + Unable to commit the library database: %1 + Não foi possível confirmar a transação no banco de dados da biblioteca: %1 + + LibraryInfoView @@ -1009,110 +1658,203 @@ Número de quadrinhos lidos + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Esta biblioteca está ocupada. Aguarde o fim da manutenção. Se parecer travada, feche o aplicativo mostrado em Detalhes e tente novamente. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Uma manutenção anterior pode não ter liberado esta biblioteca. Antes de recuperar o acesso, confirme que nenhuma operação do YACReaderLibrary ou YACReaderLibraryServer a está usando, inclusive em outro computador. + + + + Retry + Tentar novamente + + + + Recover and retry + Recuperar e tentar novamente + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + O proprietário do bloqueio da biblioteca mudou. Tente a operação novamente e verifique o novo proprietário antes de recuperar o acesso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Não foi possível remover o bloqueio de manutenção: %1. Confirme que o arquivo não é somente leitura e que você pode gravar na pasta de dados da biblioteca, e tente novamente. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Uma pasta ocupa o caminho do arquivo de bloqueio de manutenção: %1. Renomeie ou mova essa pasta antes de tentar novamente. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Não foi possível criar o arquivo de bloqueio da biblioteca: %1. Verifique as permissões de gravação da pasta de dados da biblioteca. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + A pasta de dados da biblioteca está ausente: %1. Restaure a pasta ou recrie a biblioteca antes de atualizá-la. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + A biblioteca tem um bloqueio de manutenção cujo proprietário não pôde ser verificado (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Outra operação de manutenção está usando esta biblioteca (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Não foi possível criar o arquivo de bloqueio da biblioteca: %1. Verifique o espaço disponível em disco e o acesso à pasta de dados da biblioteca. + + LibraryManagementCoordinator - + Error opening the library Erro ao abrir a biblioteca - + Error creating the library Erro ao criar a biblioteca - + Error updating the library Erro ao atualizar a biblioteca + + + + XML information rescan + Nova varredura de informações XML + + + + Restore recovery + Recuperação de restauração + + + + Library upgrade + Atualização da biblioteca + + + + Library maintenance + Manutenção da biblioteca + + + + Recover library access + Recuperar acesso à biblioteca + LibraryWindow - - Do you want remove - Você deseja remover + + Do you want to remove the library '%1'? + Você deseja remover a biblioteca '%1'? - + YACReader Library Biblioteca YACReader - + Are you sure? Você tem certeza? + Delete folder Excluir pasta - + Upgrade failed Falha na atualização - + There were errors during library upgrade in: Ocorreram erros durante a atualização da biblioteca em: - + Restore recovery failed - Falha na recuperação do restauro + Falha na recuperação da restauração - + Update needed Atualização necessária - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - Esta biblioteca foi criada com uma versão anterior do YACReaderLibrary. Ele precisa ser atualizado. Atualizar agora? + Esta biblioteca foi criada com uma versão anterior do YACReaderLibrary. Ela precisa ser atualizada. Atualizar agora? - + Download new version - Baixe a nova versão + Baixar nova versão - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - Esta biblioteca foi criada com uma versão mais recente do YACReaderLibrary. Baixe a nova versão agora? + Esta biblioteca foi criada com uma versão mais recente do YACReaderLibrary. Deseja baixar a nova versão agora? - + Library not available Biblioteca não disponível - + Library '%1' is no longer available. Do you want to remove it? - A biblioteca '%1' não está mais disponível. Você quer removê-lo? + A biblioteca '%1' não está mais disponível. Você quer removê-la? - + Old library Biblioteca antiga - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - A biblioteca '%1' foi criada com uma versão mais antiga do YACReaderLibrary. Deve ser criado novamente. Deseja criar a biblioteca agora? + A biblioteca '%1' foi criada com uma versão mais antiga do YACReaderLibrary. Ela deve ser criada novamente. Deseja criar a biblioteca agora? - + Copying comics... Copiando quadrinhos... - + Moving comics... - Quadrinhos em movimento... + Movendo quadrinhos... @@ -1130,7 +1872,7 @@ - Please, select a folder first + Please select a folder first Por favor, selecione uma pasta primeiro @@ -1149,84 +1891,113 @@ A pasta selecionada e todo o seu conteúdo serão excluídos do disco. Tem certeza? - + Unable to delete Não foi possível excluir - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. Ocorreu um problema ao tentar excluir as pastas selecionadas. Por favor, verifique as permissões de gravação e certifique-se de que nenhum aplicativo esteja usando essas pastas ou qualquer um dos arquivos contidos. - + + Edit smart list... + Editar lista inteligente... + + + + Delete smart list + Excluir lista inteligente + + + + + + New folder... + Nova pasta... + + + + + + Move to folder... + Mover para pasta... + + + + Rename folder... + Renomear pasta... + + + + Match to library comics... + Associar aos quadrinhos da biblioteca... + + + + Track missing issues... + Registrar edições ausentes... + + + + Export reading list... + Exportar lista de leitura... + + + Rename or organize files Renomear ou organizar arquivos - + Set the type of the selected comics Definir o tipo dos quadrinhos selecionados - + + Save as smart list… + Salvar como lista inteligente… + + + Search filters Filtros de pesquisa - + Unread Não lidos - + In progress Em andamento - + Highly rated Bem avaliados - + Recently added Adicionados recentemente - + Search syntax… Sintaxe de pesquisa… - - A repair of this library is already running (%1). Wait for it to finish. - Uma reparação desta biblioteca já está em execução (%1). Aguarde a conclusão. - - - - The library is locked by a repair that did not finish. - A biblioteca está bloqueada por uma reparação que não terminou. - - - - The library is locked by a repair started by %1. - A biblioteca está bloqueada por uma reparação iniciada por %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Se tem certeza de que nenhuma outra reparação está em execução, o bloqueio pode ser removido. Remover o bloqueio e continuar? - - - + Package operation failed Falha na operação de pacote - + The covers package operation could not be completed. Não foi possível concluir a operação com o pacote de capas. @@ -1277,22 +2048,22 @@ Pasta: %1 Não foi possível atualizar o banco de dados da biblioteca nem reverter a renomeação da pasta no disco. Agora a biblioteca precisa ser atualizada manualmente. - + Save covers Salvar capas - + You are adding too many libraries. Você está adicionando muitas bibliotecas. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. Você está adicionando muitas bibliotecas. Você provavelmente só precisa de uma biblioteca em sua pasta de quadrinhos de nível superior. Você pode navegar em qualquer subpasta usando a seção de pastas na barra lateral esquerda. @@ -1300,142 +2071,142 @@ Você provavelmente só precisa de uma biblioteca em sua pasta de quadrinhos de YACReaderLibrary não impedirá você de criar mais bibliotecas, mas você deve manter o número de bibliotecas baixo. - + Library not found Biblioteca não encontrada - + The selected folder doesn't contain any library. A pasta selecionada não contém nenhuma biblioteca. - - + + YACReader library database (*.ydb) - Base de dados da biblioteca YACReader (*.ydb) + Banco de dados da biblioteca do YACReader (*.ydb) - + The library database backup was created at: %1 - A cópia de segurança da base de dados da biblioteca foi criada em: + O backup do banco de dados da biblioteca foi criado em: %1 - + Unable to create the library database backup: %1 - Não foi possível criar a cópia de segurança da base de dados da biblioteca: + Não foi possível criar o backup do banco de dados da biblioteca: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? - Feche o YACReaderLibraryServer e qualquer outra aplicação YACReader que esteja a usar esta biblioteca antes de restaurar. Continuar? + Feche o YACReaderLibraryServer e qualquer outro aplicativo YACReader que esteja usando esta biblioteca antes de restaurar. Continuar? - + Restoring library database... - A restaurar a base de dados da biblioteca... + Restaurando o banco de dados da biblioteca... - + The current library database is invalid. Restore the selected backup anyway? - A base de dados atual da biblioteca não é válida. Restaurar a cópia de segurança selecionada mesmo assim? - - - - - The library maintenance lock may be stale. Remove it and retry? - O bloqueio de manutenção da biblioteca pode estar obsoleto. Removê-lo e tentar novamente? + O banco de dados atual da biblioteca é inválido. Restaurar o backup selecionado mesmo assim? - + Restart YACReaderLibrary before attempting recovery again. -Reinicie o YACReaderLibrary antes de tentar novamente a recuperação. +Reinicie o YACReaderLibrary antes de tentar a recuperação novamente. - + The library database was restored successfully. Update the library now? - A base de dados da biblioteca foi restaurada com êxito. Atualizar a biblioteca agora? + O banco de dados da biblioteca foi restaurado com sucesso. Atualizar a biblioteca agora? - + Library database damaged - Base de dados da biblioteca danificada + Banco de dados da biblioteca danificado - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. - A base de dados da biblioteca '%1' está danificada, pelo que as atualizações, a manutenção e as cópias de segurança normais não estão disponíveis. O YACReader pode tentar reparar a base de dados. Alguns dados danificados poderão não ser recuperados. As cópias de segurança existentes não serão alteradas. + O banco de dados da biblioteca '%1' está danificado, portanto as atualizações, a manutenção e os backups normais não estão disponíveis. O YACReader pode tentar reparar o banco de dados. Alguns dados danificados podem não ser recuperáveis. Os backups existentes não serão alterados. - + Attempt repair Tentar reparar - + Restore a backup... - Restaurar uma cópia de segurança... + Restaurar um backup... - + Repairing library database... - A reparar a base de dados da biblioteca... + Reparando o banco de dados da biblioteca... - - + + + Library database repair - Reparação da base de dados da biblioteca + Reparo do banco de dados da biblioteca + + + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Verificar o banco de dados da biblioteca '%1' e tentar repará-lo se necessário? Os backups existentes não serão alterados. - - Another maintenance operation is currently using this library. Try again after it finishes. - Outra operação de manutenção está a usar esta biblioteca. Tente novamente quando terminar. + + Check and repair + Verificar e reparar - + The library database is already valid. - A base de dados da biblioteca já é válida. + O banco de dados da biblioteca já é válido. - + Library database repaired - Base de dados da biblioteca reparada + Banco de dados da biblioteca reparado - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 - A base de dados da biblioteca foi reparada através da reconstrução dos índices. O original danificado foi preservado em: + O banco de dados da biblioteca foi reparado com a reconstrução dos índices. O original danificado foi preservado em: %1 - + Library database rebuilt - Base de dados da biblioteca reconstruída + Banco de dados da biblioteca reconstruído - + The library database was rebuilt successfully. The damaged original was preserved at: %1 Update the library now? - A base de dados da biblioteca foi reconstruída com êxito. O original danificado foi preservado em: + O banco de dados da biblioteca foi reconstruído com sucesso. O original danificado foi preservado em: %1 Atualizar a biblioteca agora? - + The damaged original was preserved at: @@ -1446,111 +2217,143 @@ O original danificado foi preservado em: %1 - + Library database repair failed - Falha ao reparar a base de dados da biblioteca + Falha ao reparar o banco de dados da biblioteca - + The library database could not be repaired: %1%2 You can restore a backup from the Library menu or recreate the library. - Não foi possível reparar a base de dados da biblioteca: + Não foi possível reparar o banco de dados da biblioteca: %1%2 -Pode restaurar uma cópia de segurança no menu Biblioteca ou recriar a biblioteca. - - - - library? - biblioteca? +Você pode restaurar um backup no menu Biblioteca ou recriar a biblioteca. - + Remove and delete metadata and backups - Remover e eliminar metadados e cópias de segurança + Remover e excluir metadados e backups - Remove and delete metadata - Remover e excluir metadados - - - + Library info Informações da biblioteca - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. Ocorreu um problema ao tentar excluir os quadrinhos selecionados. Por favor, verifique as permissões de gravação nos arquivos selecionados ou na pasta que os contém. - + Assign comics numbers Atribuir números de quadrinhos - - Assign numbers starting in: + + Assign numbers starting at: Atribua números começando em: - + Invalid image Imagem inválida - + The selected file is not a valid image. O arquivo selecionado não é uma imagem válida. - + + Error saving cover Erro ao salvar a capa - + + There was an error saving the cover image. Ocorreu um erro ao salvar a imagem da capa. - + + + Match to library comic + Associar a um quadrinho da biblioteca + + + + This also gives the comic to entries in %n other list(s): %1 + + Isso também associa o quadrinho a entradas de %n outra lista: %1 + Isso também associa o quadrinho a entradas de %n outras listas: %1 + + + + + The comic could not be matched. Nothing was changed. + Não foi possível associar o quadrinho. Nada foi alterado. + + + + The selected missing comics will be removed from this folder. Are you sure? + Os quadrinhos ausentes selecionados serão removidos desta pasta. Tem certeza? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + Os quadrinhos selecionados serão excluídos do disco, e os quadrinhos ausentes selecionados serão removidos desta pasta. Tem certeza? + + + Delete comics Excluir quadrinhos - + All the selected comics will be deleted from your disk. Are you sure? Todos os quadrinhos selecionados serão excluídos do seu disco. Tem certeza? - + + Remove the comics from Reading? Their reading progress will be reset. + Remover os quadrinhos de Em leitura? O progresso de leitura será redefinido. + + + + The comics will be removed from Favorites. Are you sure? + Os quadrinhos serão removidos dos Favoritos. Tem certeza? + + + Remove comics Remover quadrinhos - + Comics will only be deleted from the current label/list. Are you sure? Os quadrinhos serão excluídos apenas do rótulo/lista atual. Tem certeza? - + Library name already exists O nome da biblioteca já existe - + There is another library with the name '%1'. Existe outra biblioteca com o nome '%1'. - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1575,13 +2378,13 @@ Arquivos ausentes: %3 Export comics info - Exportar informa??es dos quadrinhos + Exportar informações dos quadrinhos Import comics info - Importar informa??es dos quadrinhos + Importar informações dos quadrinhos @@ -1591,7 +2394,7 @@ Arquivos ausentes: %3 Pack the covers of the selected library - Pacote de capas da biblioteca selecionada + Empacotar as capas da biblioteca selecionada @@ -1616,454 +2419,525 @@ Arquivos ausentes: %3 Back up library database - Criar cópia de segurança da base de dados + Fazer backup do banco de dados da biblioteca Create a backup of the current library database - Criar uma cópia de segurança da base de dados atual da biblioteca + Criar um backup do banco de dados atual da biblioteca Restore library database backup - Restaurar cópia de segurança da base de dados + Restaurar backup do banco de dados da biblioteca Restore the current library database from a backup - Restaurar a base de dados atual da biblioteca a partir de uma cópia de segurança + Restaurar o banco de dados atual da biblioteca a partir de um backup + Repair library database... + Reparar banco de dados da biblioteca... + + + + Check the current library database and attempt to repair any damage + Verificar o banco de dados da biblioteca atual e tentar reparar danos + + + + Recover library access... + Recuperar acesso à biblioteca... + + + + Recover access after an interrupted library maintenance operation + Recuperar o acesso após uma manutenção interrompida da biblioteca + + + Repair covers and comic info Reparar capas e informações dos quadrinhos - + Retry comics with missing covers or incomplete information Processar novamente quadrinhos com capas ausentes ou informações incompletas - + Rename library Renomear biblioteca - + Rename current library Renomear biblioteca atual - + Remove library Remover biblioteca - + Remove current library from your collection Remover biblioteca atual da sua coleção - + Rescan library for XML info - Reanalisar biblioteca para informa??es XML + Reanalisar biblioteca para informações XML - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Tenta encontrar informações XML incorporadas em arquivos de quadrinhos. Você só precisa fazer isso se a biblioteca foi criada com versões 9.8.2 ou anteriores ou se você estiver usando software de terceiros para incorporar informações XML nos arquivos. - + Open library folder... Abrir pasta da biblioteca... - + Open the root folder of the current library Abrir a pasta raiz da biblioteca atual - + Show library info - Mostrar informa??es da biblioteca + Mostrar informações da biblioteca - + Show information about the current library Mostrar informações sobre a biblioteca atual - + Open current comic Abrir quadrinho atual - - Open current comic on YACReader + + Open current comic in YACReader Abrir quadrinho atual no YACReader - + Save selected covers to... Salvar capas selecionadas em... - + Save covers of the selected comics as JPG files - Salve as capas dos quadrinhos selecionados como arquivos JPG + Salvar as capas dos quadrinhos selecionados como arquivos JPG - - + + Set as read Definir como lido - + Set comic as read Definir quadrinhos como lidos - - + + Set as unread Definir como não lido - + Set comic as unread Definir quadrinhos como não lidos - - + + manga mangá - + Set issue as manga - Definir problema como mangá + Definir edição como mangá - - + + comic - cômico + quadrinho - + Set issue as normal - Defina o problema como normal + Definir edição como normal - + western manga mangá ocidental - + Set issue as western manga - Definir problema como mangá ocidental + Definir edição como mangá ocidental - - + + web comic - quadrinhos da web + quadrinho da web - + Set issue as web comic - Definir o problema como web comic + Definir edição como quadrinho da web - - + + yonkoma tira yonkoma - + Set issue as yonkoma - Definir problema como yonkoma + Definir edição como yonkoma - + Show/Hide marks Mostrar/ocultar marcas - + Show or hide read marks Mostrar ou ocultar marcas de leitura - + Show/Hide recent indicator Mostrar/ocultar indicador recente - + Show or hide recent indicator Mostrar ou ocultar indicador recente - - + + Fullscreen mode on/off Modo tela cheia ativado/desativado - + Help, About YACReader Ajuda, Sobre o YACReader - + Add new folder Adicionar nova pasta - + Add new folder to the current library Adicionar nova pasta à biblioteca atual - + Rename folder Renomear pasta - + Rename the current folder on disk and in the library Renomear a pasta atual no disco e na biblioteca - + Delete folder Excluir pasta - + Delete current folder from disk - Exclua a pasta atual do disco + Excluir a pasta atual do disco - + Select root node Selecionar raiz - + Expand all nodes - Expandir todos + Expandir todos os nós - + Collapse all nodes Recolher todos os nós - + Show options dialog Mostrar opções - + Show comics server options dialog Mostrar caixa de diálogo de opções do servidor de quadrinhos - - + + Change between comics views - Alterar entre visualizações de quadrinhos + Alternar entre visualizações de quadrinhos - + + Grid view + Visualização em grade + + + + Flow view + + + + + Info view + + + + Open folder... Abrir pasta... - - + + Rename files... Organize files Renomear arquivos... - - + + Organize into folders... Organizar em pastas... - + Set as uncompleted Definir como incompleto - + Set as completed Definir como concluído - + Set custom cover Definir capa personalizada - + Delete custom cover Excluir capa personalizada - + western manga (left to right) mangá ocidental (da esquerda para a direita) - + Open containing folder... - Abrir a pasta contendo... + Abrir a pasta que contém o arquivo... - Reset comic rating - Redefinir classificação de quadrinhos + + New reading list + Nova lista de leitura - + Select all comics - Selecione todos os quadrinhos + Selecionar todos os quadrinhos - + Edit Editar - + Assign current order to comics Atribuir ordem atual aos quadrinhos - + Update cover Atualizar capa - + Delete selected comics Excluir quadrinhos selecionados - + Delete metadata from selected comics Excluir metadados dos quadrinhos selecionados - + Download tags from Comic Vine - Baixe tags do Comic Vine + Baixar tags do Comic Vine - + Focus search line - Linha de pesquisa de foco + Focar no campo de pesquisa - + Focus comics view - Visualização de quadrinhos em foco + Focar na visualização de quadrinhos - + Edit shortcuts Editar atalhos - + &Quit - &Qfato + &Sair - + Update folder Atualizar pasta - + Update current folder Atualizar pasta atual - + Scan legacy XML metadata - Digitalize metadados XML legados + Verificar metadados XML legados - - Add new reading list - Adicionar nova lista de leitura + + Add a new reading list to the current library + Adicionar uma nova lista de leitura à biblioteca atual - - Add a new reading list to the current library - Adicione uma nova lista de leitura à biblioteca atual + + New smart list... + Nova lista inteligente... + + + + Add a saved search to the current library + Adicionar uma pesquisa salva à biblioteca atual + + + + New folder... + Nova pasta... + + + + Add a folder for reading lists or smart lists + Adicionar uma pasta para listas de leitura ou listas inteligentes + + + + Import reading lists... + Importar listas de leitura... + + + + Import CBL reading lists into the current library + Importar listas de leitura CBL para a biblioteca atual + + + + New + Novo + + + + Add or import reading lists + Adicionar ou importar listas de leitura - + Remove reading list Remover lista de leitura - + Remove current reading list from the library Remover lista de leitura atual da biblioteca - + Add new label Adicionar novo rótulo - + Add a new label to this library - Adicione um novo rótulo a esta biblioteca + Adicionar um novo rótulo a esta biblioteca - + Rename selected list Renomear lista selecionada - + Rename any selected labels or lists - Renomeie quaisquer rótulos ou listas selecionados + Renomear quaisquer rótulos ou listas selecionados - + Add to... - Adicionar à... + Adicionar a... - + Favorites Favoritos - + Add selected comics to favorites list - Adicione quadrinhos selecionados à lista de favoritos + Adicionar os quadrinhos selecionados à lista de favoritos - - + + Reset rating - Redefinir classificação + Redefinir avaliação @@ -2071,7 +2945,7 @@ Arquivos ausentes: %3 comic - cômico + quadrinho @@ -2086,95 +2960,108 @@ Arquivos ausentes: %3 web comic - quadrinhos da web + quadrinho da web - 4koma (top to botom) + 4koma (top to bottom) 4koma (de cima para baixo) - - - + + + Set type Definir tipo - + Library Biblioteca - + Folder Pasta - + Comic - Quadrinhos + Quadrinho - + + Match to library comic... + Associar a um quadrinho da biblioteca... + + + + + + + Track missing issues... + Registrar edições ausentes... + + + Open folder... Abrir pasta... - + Update folder Atualizar pasta - + Rename folder Renomear pasta - + Rename files... Renomear arquivos... - + Organize into folders... Organizar em pastas... - + Rescan library for XML info - Reanalisar biblioteca para informa??es XML + Reanalisar biblioteca para informações XML - + Set as uncompleted Definir como incompleto - + Set as completed Definir como concluído - + Set as read Definir como lido - - + + Set as unread Definir como não lido - + Set custom cover Definir capa personalizada - - + + Delete custom cover Excluir capa personalizada @@ -2207,17 +3094,63 @@ Arquivos ausentes: %3 1 sublista - - %1 sublists - %1 sublistas + + %1 sublists + %1 sublistas + + + + LocalComicListModel + + + file name + nome do arquivo + + + + MoveToFolderDialog + + + Move to folder + Mover para pasta + + + + Top level + Nível superior + + + + Move "%1" to: + Mover "%1" para: + + + + NewListFolderDialog + + + New folder + Nova pasta + + + + Reading lists + Listas de leitura + + + + Smart lists + Listas inteligentes - - - LocalComicListModel - - file name - nome do arquivo + + Folder name: + Nome da pasta: + + + + For: + Para: @@ -2229,7 +3162,7 @@ Arquivos ausentes: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> <p>Você pode criar uma biblioteca em qualquer pasta, YACReaderLibrary importará todos os quadrinhos e pastas desta pasta. Se você já criou alguma biblioteca, poderá abri-la.</p><p>Não se esqueça de que você pode usar o YACReader como um aplicativo independente para ler quadrinhos em seu computador.</p> @@ -2240,7 +3173,7 @@ Arquivos ausentes: %3 add an existing one - adicione um existente + adicione uma existente @@ -2276,12 +3209,12 @@ Arquivos ausentes: %3 Close to tray - Perto da bandeja + Fechar na bandeja - Start into the system tray - Comece na bandeja do sistema + Start in the system tray + Iniciar na bandeja do sistema @@ -2302,12 +3235,12 @@ Arquivos ausentes: %3 Import metadata from ComicInfo.xml when adding new comics Import metada from ComicInfo.xml when adding new comics - Importe metadados de ComicInfo.xml ao adicionar novos quadrinhos + Importar metadados de ComicInfo.xml ao adicionar novos quadrinhos Consider 'recent' items added or updated since X days ago - Considere itens 'recentes' adicionados ou atualizados há X dias + Considerar itens 'recentes' adicionados ou atualizados há X dias @@ -2322,7 +3255,7 @@ Arquivos ausentes: %3 Clear - Claro + Limpar @@ -2332,12 +3265,12 @@ Arquivos ausentes: %3 Try to detect changes automatically - Tente detectar alterações automaticamente + Tentar detectar alterações automaticamente Update libraries periodically - Atualize bibliotecas periodicamente + Atualizar bibliotecas periodicamente @@ -2387,14 +3320,14 @@ Arquivos ausentes: %3 Time: - Tempo: + Horário: - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2411,7 +3344,7 @@ Para interromper uma atualização automática, toque no indicador de carregamen Compare the modified date of files when updating a library (not recommended) - Compare a data de modificação dos arquivos ao atualizar uma biblioteca (não recomendado) + Comparar a data de modificação dos arquivos ao atualizar uma biblioteca (não recomendado) @@ -2431,11 +3364,11 @@ Para interromper uma atualização automática, toque no indicador de carregamen Use selected comic cover as background - Use a capa de quadrinhos selecionada como plano de fundo + Usar a capa do quadrinho selecionado como plano de fundo - Restore defautls + Restore defaults Restaurar padrões @@ -2451,7 +3384,7 @@ Para interromper uma atualização automática, toque no indicador de carregamen Display current comic banner - Exibir banner de quadrinhos atual + Exibir banner do quadrinho atual @@ -2492,7 +3425,7 @@ Para interromper uma atualização automática, toque no indicador de carregamen General - Em geral + Geral @@ -2507,18 +3440,99 @@ Para interromper uma atualização automática, toque no indicador de carregamen Restart is needed - Reiniciar é necessário + É necessário reiniciar OrganizeFiles - + + + Unknown Series + Série desconhecida + + + + series + série + + + + Unknown Publisher + Editora desconhecida + + + + publisher + editora + + + + title + título + + + + Unknown Comic + Quadrinho desconhecido + + + + Series #Number - Title + Série #Número - Título + + + + Series #Number + Série #Número + + + + Series #Number (of Count) + Série #Número (de Total) + + + + Number - Title + Número - Título + + + + Series #Number (Year) + Série #Número (Ano) + + + + Publisher / Series / Series #Number - Title + Editora / Série / Série #Número - Título + + + + Publisher / Imprint / Series / Series #Number - Title + Editora / Selo editorial / Série / Série #Número - Título + + + + Series / Series #Number + Série / Série #Número + + + + Publisher / Series / Series #Number (Year) + Editora / Série / Série #Número (Ano) + + + + Series / original file name + Série / nome original do arquivo + + + Renamed, %1 is already in use Renomeado, %1 já está em uso - + Missing metadata: %1 Metadados ausentes: %1 @@ -2527,62 +3541,82 @@ Para interromper uma atualização automática, toque no indicador de carregamen %1 could not be created Não foi possível criar %1 + + + %1 (copy also failed: %2) + %1 (a cópia também falhou: %2) + + + + the copy did not have the same size as the original + a cópia não tinha o mesmo tamanho do original + + + + the original could not be deleted after it was copied + não foi possível excluir o original após a cópia + + + + The destination folder could not be created. + Não foi possível criar a pasta de destino. + + + + %1 (the file was left at %2) + %1 (o arquivo foi deixado em %2) + OrganizeFilesCoordinator - - + + Organize files Organizar arquivos - + This folder does not contain any comics. Esta pasta não contém nenhum quadrinho. - - This library is busy: %1 - Esta biblioteca está ocupada: %1 - - - + the library database could not be opened não foi possível abrir o banco de dados da biblioteca - + the library database could not be locked for writing não foi possível bloquear o banco de dados da biblioteca para gravação - + a folder entry could not be restored não foi possível restaurar uma entrada de pasta - + a comic entry could not be updated não foi possível atualizar uma entrada de quadrinho - + the library database could not be saved: %1 não foi possível salvar o banco de dados da biblioteca: %1 - + the record of the last organize run could not be read não foi possível ler o registro da última organização - + the folder %1 could not be created não foi possível criar a pasta %1 - + %n file(s) could not be moved back não foi possível mover %n arquivo de volta @@ -2592,10 +3626,6 @@ Para interromper uma atualização automática, toque no indicador de carregamen OrganizeFilesDialog - - Format: - Formatar: - Organize files @@ -2727,19 +3757,11 @@ Para interromper uma atualização automática, toque no indicador de carregamen Cancel Cancelar - - Copy the list - Copiar a lista - Undo Desfazer - - Close - Fechar - Copy failure details @@ -2949,7 +3971,7 @@ Para interromper uma atualização automática, toque no indicador de carregamen Status - Estado + Status @@ -3158,274 +4180,386 @@ Para interromper uma atualização automática, toque no indicador de carregamen PropertiesDialog - + General info Informações gerais - + Authors Autores - + Publishing Publicação - + Plot - Trama + Enredo - + Notes Notas - + Cover page - Página de rosto + Página da capa - + Load previous page as cover Carregar página anterior como capa - + Load next page as cover Carregar a próxima página como capa - + Reset cover to the default image Redefinir a capa para a imagem padrão - + Load custom cover image Carregar imagem de capa personalizada - + Series: Série: - + Title: Título: - - + + e.g. 1-5, 8, 0.5, Annual 1 + ex.: 1-5, 8, 0.5, Annual 1 + + + + of: de: - + + Issue number: - Número de emissão: + Número da edição: - + Volume: - Tomo: + Volume: - + Arc number: Número do arco: - + Story arc: Arco da história: - + alt. number: - alt. número: + núm. alt.: - + Alternate series: Série alternativa: - + Series Group: Grupo de séries: - + Genre: Gênero: - + Size: Tamanho: - + Writer(s): - Escritor(es): + Roteirista(s): - + Penciller(s): - Desenhador(es): + Desenhista(s): - + Inker(s): - Tinteiro(s): + Arte-finalista(s): - + Colorist(s): Colorista(s): - + Letterer(s): Letrista(s): - + Cover Artist(s): Artista(s) da capa: - + Editor(s): Editor(es): - + Imprint: - Imprimir: + Selo editorial: - + Day: Dia: - + Month: Mês: - + Year: Ano: - + Publisher: - Editor: + Editora: - + Format: - Formatar: + Formato: - + Color/BW: Cor/PB: - + Age rating: Classificação etária: - + Type: Tipo: - + Language (ISO): Idioma (ISO): - + Synopsis: Sinopse: - + Characters: Personagens: - + Teams: Equipes: - + Locations: Locais: - + Main character or team: Personagem principal ou equipe: - + Review: - Análise: + Resenha: - + Notes: Notas: - + Tags: - Etiquetas: + Tags: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> Link do Comic Vine: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> visualizar </a> - + Not found Não encontrado - + Comic not found. You should update your library. Quadrinho não encontrado. Você deve atualizar sua biblioteca. - + Edit selected comics information - Edite as informações dos quadrinhos selecionados + Editar informações dos quadrinhos selecionados + + + + + Track missing issues in "%1" + Registrar edições ausentes em "%1" + + + + Issues: + Edições: + + + + Track + Registrar + + + + At most %1 issues can be tracked at once. + É possível registrar no máximo %1 edições de uma vez. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Verifique as edições: os intervalos devem ser crescentes e não pode haver partes vazias entre vírgulas. + + + + No new issues to track. + Nenhuma edição nova para registrar. + + + + %n issue(s) will be tracked: %1. + + %n edição será registrada: %1. + %n edições serão registradas: %1. + + + + + That is a lot of issues; check the numbers. + São muitas edições; confira os números. + + + + %n issue(s) already in the folder. + + %n edição já está na pasta. + %n edições já estão na pasta. + + + + + %n repeated issue(s). + + %n edição repetida. + %n edições repetidas. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Edições não registradas + + + + The missing issues could not be tracked. + Não foi possível registrar as edições ausentes. + + + + Comic skipped + Quadrinho ignorado + + + + A comic was removed from the library or now has a different file, so it is skipped. + Um quadrinho foi removido da biblioteca ou agora tem um arquivo diferente, por isso será ignorado. - + + + Changes not saved + Alterações não salvas + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Um quadrinho que você está editando foi removido da biblioteca ou agora tem um arquivo diferente. Suas alterações não foram salvas. + + + + The changes could not be saved. + Não foi possível salvar as alterações. + + + Invalid cover Capa inválida - + The image is invalid. A imagem é inválida. - + Edit comic information Editar informações dos quadrinhos @@ -3440,15 +4574,15 @@ Para interromper uma atualização automática, toque no indicador de carregamen unable to load 7z lib from ./utils - não é possível carregar 7z lib de ./utils + não foi possível carregar a biblioteca 7z de ./utils - + Select custom cover Selecione a capa personalizada - + Images (%1) Imagens (%1) @@ -3481,31 +4615,166 @@ Para interromper uma atualização automática, toque no indicador de carregamen ReadingListManagementCoordinator - + Add new reading lists - Adicione novas listas de leitura + Adicionar novas listas de leitura - - + + List name: Nome da lista: - + + Import reading lists + Importar listas de leitura + + + + Reading lists (*.cbl *.json);;All files (*) + Listas de leitura (*.cbl *.json);;Todos os arquivos (*) + + + + New smart list + Nova lista inteligente + + + + + The smart list could not be saved. + Não foi possível salvar a lista inteligente. + + + + Edit smart list + Editar lista inteligente + + + + Delete folder + Excluir pasta + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Excluir a pasta "%1" e todo o seu conteúdo? As pastas e listas serão excluídas; seus arquivos de quadrinhos serão mantidos. + + + Delete list/label Excluir lista/rótulo - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? O item selecionado será excluído, seus quadrinhos ou pastas NÃO serão excluídos do disco. Tem certeza? - + Rename list name Renomear nome da lista + + + Rename folder + Renomear pasta + + + + + Folder name: + Nome da pasta: + + + + + + New folder + Nova pasta + + + + + The folder could not be created. + Não foi possível criar a pasta. + + + + Reading list + Lista de leitura + + + + CBL reading list (*.cbl) + Lista de leitura CBL (*.cbl) + + + + JSON reading list (*.json) + Lista de leitura JSON (*.json) + + + + Web page (*.html) + Página da web (*.html) + + + + + Export reading list + Exportar lista de leitura + + + + Exporting reading list... + Exportando lista de leitura... + + + + The file could not be written. + Não foi possível gravar o arquivo. + + + + Move to folder + Mover para pasta + + + + "%1" could not be moved. + Não foi possível mover "%1". + + + + ReadingListPage + + + %n comic(s) + + %n quadrinho + %n quadrinhos + + + + + %n missing + + %n ausente + %n ausentes + + + + + Missing + Ausente + + + + Exported from YACReader on %1 + Exportado do YACReader em %1 + RenameLibraryDialog @@ -3526,15 +4795,15 @@ Para interromper uma atualização automática, toque no indicador de carregamen - New Library Name : - Novo nome da biblioteca : + New Library Name: + Novo nome da biblioteca: ScraperResultsPaginator - Number of volumes found : %1 + Number of volumes found: %1 Número de volumes encontrados: %1 @@ -3545,8 +4814,8 @@ Para interromper uma atualização automática, toque no indicador de carregamen - Number of %1 found : %2 - Número de %1 encontrado: %2 + Number of %1 found: %2 + Número de %1 encontrados: %2 @@ -3828,7 +5097,7 @@ Para interromper uma atualização automática, toque no indicador de carregamen Please provide some additional information for this comic. Please provide some additional information. - Forneça algumas informações adicionais para esta história em quadrinhos. + Forneça algumas informações adicionais para este quadrinho. @@ -3908,6 +5177,11 @@ Para interromper uma atualização automática, toque no indicador de carregamen Just start typing. Plain text search across all metadata. Basta começar a digitar. O texto simples é pesquisado em todos os metadados. + + + %1 -> search for `%1` in any database field + %1 -> pesquisar `%1` em qualquer campo do banco de dados + 1. Search everywhere @@ -4003,7 +5277,9 @@ Para interromper uma atualização automática, toque no indicador de carregamen Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Dicas: os espaços funcionam como AND e as pesquisas não diferenciam maiúsculas de minúsculas. Use aspas para incluir espaços em um valor. + Dicas: +Os espaços funcionam como AND e as pesquisas não diferenciam maiúsculas de minúsculas. +Use aspas para incluir espaços em um valor. @@ -4158,7 +5434,7 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. + Please select the right comic info. Por favor, selecione as informações corretas dos quadrinhos. @@ -4169,12 +5445,12 @@ Use quotes to include spaces in a value. loading cover - tampa de carregamento + carregando capa loading description - descrição de carregamento + carregando descrição @@ -4186,7 +5462,7 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. Por favor, selecione a série certa para o seu quadrinho. @@ -4197,7 +5473,7 @@ Use quotes to include spaces in a value. volumes - tomos + volumes @@ -4207,12 +5483,12 @@ Use quotes to include spaces in a value. loading cover - tampa de carregamento + carregando capa loading description - descrição de carregamento + carregando descrição @@ -4225,61 +5501,66 @@ Use quotes to include spaces in a value. Unknown - Desconhecido + Desconhecido %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 desconhecidas) %1 series - + %1 séries - + %1 comics selected %n comic(s) selected - %1 quadrinhos selecionados + %1 quadrinhos selecionados - + Read - Ler + Lidos - + In progress - Em andamento + Em andamento - + Unread - Não lidos + Não lidos - + Total pages - + Total de páginas - + Total size - + Tamanho total + + + + Missing + Ausente - + Series - Série + Série SeriesQuestion - You are trying to get information for various comics at once, are they part of the same series? + You are trying to get information for various comics at once. Are they part of the same series? Você está tentando obter informações sobre vários quadrinhos ao mesmo tempo. Eles fazem parte da mesma série? @@ -4301,22 +5582,6 @@ Use quotes to include spaces in a value. set port Definir porta - - Server connectivity information - Informações de conectividade do servidor - - - Scan it! - Digitalize! - - - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - O YACReader está disponível para dispositivos iOS e Android.<br/>Descubra-o para <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> ou <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - - - Choose an IP address - Escolha um endereço IP - @@ -4326,12 +5591,12 @@ Use quotes to include spaces in a value. Scan to connect - Digitalize para ligar + Escaneie para conectar Devices on this network can reach your library at the address below. - Os dispositivos nesta rede podem aceder à sua biblioteca através do endereço abaixo. + Os dispositivos nesta rede podem acessar sua biblioteca pelo endereço abaixo. @@ -4351,7 +5616,7 @@ Use quotes to include spaces in a value. Copy link - Copiar ligação + Copiar link @@ -4368,38 +5633,70 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. O YACReader está disponível para iOS e Android. Descubra-o para <a href='https://ios.yacreader.com'>iOS</a> ou <a href='https://android.yacreader.com'>Android</a>. + + + SmartListDialog + + + New smart list + Nova lista inteligente + + + + Edit smart list + Editar lista inteligente + - enable the server - habilitar o servidor + + Search filters and syntax + Filtros e sintaxe de pesquisa + + + + Name: + Nome: + + + + Search: + Pesquisa: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. Por favor, classifique a lista de quadrinhos à esquerda até que corresponda às informações dos quadrinhos. - + sort comics to match comic information classifique os quadrinhos para corresponder às informações dos quadrinhos - + issues - problemas + edições - + remove selected comics remover quadrinhos selecionados - + restore all removed comics restaurar todos os quadrinhos removidos + + + Add the %n other issue(s) of this volume as missing comics + + Adicionar a outra %n edição deste volume como quadrinho ausente + Adicionar as outras %n edições deste volume como quadrinhos ausentes + + ThemeEditorDialog @@ -4421,7 +5718,7 @@ Use quotes to include spaces in a value. i - eu + i @@ -4436,7 +5733,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Segure para piscar o valor selecionado na UI (magenta/alternado/0↔10). Os lançamentos restauram o original. + Segure para destacar o valor selecionado na interface (magenta / alternado / 0↔10). Ao soltar, o original é restaurado. @@ -4446,7 +5743,7 @@ Use quotes to include spaces in a value. Light - Luz + Claro @@ -4456,7 +5753,7 @@ Use quotes to include spaces in a value. ID: - EU IA: + ID: @@ -4516,7 +5813,7 @@ Use quotes to include spaces in a value. true - verdadeiro + true @@ -4524,7 +5821,7 @@ Use quotes to include spaces in a value. false - falso + false @@ -4595,7 +5892,7 @@ Use quotes to include spaces in a value. Expected a JSON object. - Esperava um objeto JSON. + Era esperado um objeto JSON. @@ -4603,7 +5900,7 @@ Use quotes to include spaces in a value. SEARCH - PROCURAR + PESQUISAR @@ -4615,8 +5912,8 @@ Use quotes to include spaces in a value. - Updating.... - Atualizando.... + Updating... + Atualizando... @@ -4624,6 +5921,14 @@ Use quotes to include spaces in a value. Atualizar biblioteca + + ViewSelector + + + View + + + VolumeComicsModel @@ -4642,12 +5947,12 @@ Use quotes to include spaces in a value. issues - problemas + edições publisher - editor + editora @@ -4665,12 +5970,12 @@ Use quotes to include spaces in a value. Stripe look - Olhar lista + Aparência em faixas Overlapped Stripe look - Olhar lista sobreposta + Aparência em faixas sobrepostas @@ -4705,17 +6010,17 @@ Use quotes to include spaces in a value. Cover gap - Cubra a lacuna + Espaço entre capas Central gap - Lacuna central + Espaço central Zoom - Amplia??o + Zoom @@ -4730,7 +6035,7 @@ Use quotes to include spaces in a value. Cover Angle - Ângulo de cobertura + Ângulo da capa @@ -4759,8 +6064,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Use VSync (melhora a qualidade da imagem em modo tela cheia, pior desempenho) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Usar VSync (melhora a qualidade da imagem em modo tela cheia, mas reduz o desempenho) @@ -4773,7 +6078,7 @@ Use quotes to include spaces in a value. &Restore - &Rloja + &Restaurar @@ -4791,7 +6096,7 @@ Use quotes to include spaces in a value. Release notes are not available. - As notas de lançamento não estão disponíveis. + As notas de versão não estão disponíveis. @@ -4799,11 +6104,49 @@ Use quotes to include spaces in a value. Versões anteriores + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + O banco de dados da biblioteca está ausente. Restaure um backup ou recrie a biblioteca antes de verificar novamente as informações XML. + + + + Unable to load the archive backend: %1 + Não foi possível carregar o mecanismo de arquivos compactados: %1 + + + + Unable to start an XML rescan transaction. + Não foi possível iniciar uma transação de nova varredura XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + A pasta selecionada não está mais nesta biblioteca. Recarregue a biblioteca antes de verificar novamente. + + + + Unable to read comics for the XML rescan. + Não foi possível ler os quadrinhos para a nova varredura XML. + + + + Unable to commit XML metadata changes. + Não foi possível confirmar as alterações nos metadados XML. + + + + Unable to update XML metadata for %1. + Não foi possível atualizar os metadados XML de %1. + + YACReaderFieldEdit - + Click to overwrite Clique para substituir @@ -4816,10 +6159,12 @@ Use quotes to include spaces in a value. YACReaderFieldPlainTextEdit + + - - - + + + Click to overwrite Clique para substituir @@ -4849,7 +6194,7 @@ Use quotes to include spaces in a value. Customize the keyboard shortcuts used by the application. - Personalize os atalhos de teclado utilizados pela aplicação. + Personalize os atalhos de teclado utilizados pelo aplicativo. diff --git a/YACReaderLibrary/yacreaderlibrary_pt_PT.ts b/YACReaderLibrary/yacreaderlibrary_pt_PT.ts new file mode 100644 index 000000000..957a631d4 --- /dev/null +++ b/YACReaderLibrary/yacreaderlibrary_pt_PT.ts @@ -0,0 +1,6213 @@ + + + + + ActionsShortcutsModel + + + None + Nenhum + + + + AddLabelDialog + + + red + Vermelho + + + + orange + Laranja + + + + yellow + Amarelo + + + + green + Verde + + + + cyan + Ciano + + + + blue + Azul + + + + violet + Violeta + + + + purple + Púrpura + + + + pink + Rosa + + + + white + Branco + + + + light + Claro + + + + dark + Escuro + + + + Label name: + Nome da etiqueta: + + + + Choose a color: + Escolha uma cor: + + + + accept + aceitar + + + + cancel + cancelar + + + + AddLibraryDialog + + + Add + Adicionar + + + + Add an existing library + Adicionar uma biblioteca existente + + + + Cancel + Cancelar + + + + Comics folder: + Pasta das bandas desenhadas: + + + + Library name: + Nome da biblioteca: + + + + ApiKeyDialog + + + Cancel + Cancelar + + + + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Antes de se poder ligar ao Comic Vine, precisa da sua própria chave de API. Obtenha uma gratuitamente <a href="http://www.comicvine.com/api/">aqui</a> + + + + Paste here your Comic Vine API key + Cole aqui a sua chave de API do Comic Vine + + + + Accept + Aceitar + + + + AppearanceTabWidget + + + Color scheme + Esquema de cores + + + + System + Sistema + + + + Light + Claro + + + + Dark + Escuro + + + + Custom + Personalizado + + + + Remove + Remover + + + + Remove this user-imported theme + Remover este tema importado pelo utilizador + + + + Light: + Claro: + + + + Dark: + Escuro: + + + + Custom: + Personalizado: + + + + Import theme... + Importar tema... + + + + Theme + Tema + + + + Theme editor + Editor de temas + + + + Open Theme Editor... + Abrir editor de temas... + + + + Theme editor error + Erro do editor de temas + + + + The current theme JSON could not be loaded. + Não foi possível carregar o JSON do tema atual. + + + + Import theme + Importar tema + + + + JSON files (*.json);;All files (*) + Ficheiros JSON (*.json);;Todos os ficheiros (*) + + + + Could not import theme from: +%1 + Não foi possível importar o tema de: +%1 + + + + Could not import theme from: +%1 + +%2 + Não foi possível importar o tema de: +%1 + +%2 + + + + Import failed + Falha na importação + + + + CblEntriesView + + + Find comic › + Encontrar banda desenhada › + + + + CblEntryDetail + + + Series + Série + + + + Number + Número + + + + Volume + Volume + + + + Year + Ano + + + + Format + Formato + + + + + ComicVine ID + ID do Comic Vine + + + + series and number + série e número + + + + series without the volume, and number + série sem o volume e número + + + + letters and digits of the series, and number + letras e algarismos da série e número + + + + series with its year, and number + série com o ano e número + + + + volume %1 + volume %1 + + + + Comic in your library + Banda desenhada na sua biblioteca + + + + Missing in your library + Em falta na sua biblioteca + + + + Changed by you. + Alterado por si. + + + + The entry has this comic. + A entrada tem esta banda desenhada. + + + + Matched by %1. + Correspondência por %1. + + + + Your library already has this comic as missing. Matched by %1. + A sua biblioteca já tem esta banda desenhada como em falta. Correspondência por %1. + + + + Possible comic %1 + Possível banda desenhada %1 + + + + + Changed by you: it is imported as missing. + Alterado por si: será importado como em falta. + + + + The comics have the same ComicVine ID. + As bandas desenhadas têm o mesmo ID do Comic Vine. + + + + The comic has the same series and number, but its volume or year is different. + A banda desenhada tem a mesma série e número, mas o volume ou o ano é diferente. + + + + The comics have the same series and number. + As bandas desenhadas têm a mesma série e número. + + + + Values that are different from the file are in amber. + Os valores diferentes dos do ficheiro aparecem a âmbar. + + + + Closest series in your library + Séries mais próximas na sua biblioteca + + + + Library + Biblioteca + + + + Has %1 + Tem %1 + + + + No comic found. Tried: %1. + Nenhuma banda desenhada encontrada. Tentativas: %1. + + + + In the file + No ficheiro + + + + Entry %1 of %2 + Entrada %1 de %2 + + + + Use this comic + Usar esta banda desenhada + + + + Use this missing comic + Usar esta banda desenhada em falta + + + + CblFindComicPage + + + ‹ All entries + ‹ Todas as entradas + + + + Previous problem + Problema anterior + + + + Next problem + Problema seguinte + + + + Folders + Pastas + + + + Search the whole library + Pesquisar em toda a biblioteca + + + + File + Ficheiro + + + + Series + Série + + + + No. + N.º + + + + Volume + Volume + + + + Year + Ano + + + + Go to the next problem after I choose + Ir para o problema seguinte após a minha escolha + + + + Use this comic + Usar esta banda desenhada + + + + Cancel + Cancelar + + + + Search in this folder + Pesquisar nesta pasta + + + + Entry %1 of %2 + Entrada %1 de %2 + + + + Missing: no comic found. Browse or search the library. + Em falta: nenhuma banda desenhada encontrada. Explore ou pesquise na biblioteca. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Ambíguo: %n banda desenhada possível. Explore ou pesquise na biblioteca. + Ambíguo: %n bandas desenhadas possíveis. Explore ou pesquise na biblioteca. + + + + + Browse or search the library to choose another comic. + Explore ou pesquise na biblioteca para escolher outra banda desenhada. + + + + Problem %1 of %2 + Problema %1 de %2 + + + + %n problem(s) + + %n problema + %n problemas + + + + + + + Library + Biblioteca + + + + Search results + Resultados da pesquisa + + + + CblImportDialog + + + Match to library comics — %1 + Associar às bandas desenhadas da biblioteca — %1 + + + + Apply + Aplicar + + + + Close + Fechar + + + + Skip + Ignorar + + + + Import + Importar + + + + &Name: + &Nome: + + + + A reading list with this name already exists. Import creates another one. + Já existe uma lista de leitura com este nome. A importação cria outra. + + + + Filter entries + Filtrar entradas + + + + Matching the books of %1 with your library... + A associar os livros de %1 à sua biblioteca... + + + + Matching the entries of %1 with your library... + A associar as entradas de %1 à sua biblioteca... + + + + + Match to library comics + Associar às bandas desenhadas da biblioteca + + + + This also gives comics to entries in %n other list(s): %1 + + Isto também associa bandas desenhadas a entradas de %n outra lista: %1 + Isto também associa bandas desenhadas a entradas de %n outras listas: %1 + + + + + The changes could not be applied. Nothing was changed. + Não foi possível aplicar as alterações. Nada foi alterado. + + + + The file cannot be opened. + Não é possível abrir o ficheiro. + + + + The file is damaged (line %1, column %2). + O ficheiro está danificado (linha %1, coluna %2). + + + + The file is not a reading list. + O ficheiro não é uma lista de leitura. + + + + The library cannot be read. + Não é possível ler a biblioteca. + + + + Import reading lists + Importar listas de leitura + + + + The reading list could not be imported. Nothing was written. + Não foi possível importar a lista de leitura. Nada foi escrito. + + + + Import reading lists — %1 (%2 of %3) + Importar listas de leitura — %1 (%2 de %3) + + + + Import reading lists — %1 + Importar listas de leitura — %1 + + + + All (%1) + Todas (%1) + + + + Matched (%1) + Associadas (%1) + + + + Ambiguous (%1) + Ambíguas (%1) + + + + Missing (%1) + Em falta (%1) + + + + Changed by you (%1) + Alteradas por si (%1) + + + + %1 of %n have a comic + + %1 de %n tem uma banda desenhada + %1 de %n têm uma banda desenhada + + + + + Find comic... + Encontrar banda desenhada... + + + + Use the original match + Usar a correspondência original + + + + Make missing + Marcar como em falta + + + + Import as missing + Importar como em falta + + + + CblImportModel + + + Matched + Associado + + + + Ambiguous + Ambíguo + + + + Missing + Em falta + + + + Changed + Alterado + + + + # + # + + + + In the file + No ficheiro + + + + Comic in your library + Banda desenhada na sua biblioteca + + + + Status + Estado + + + + Already missing in your library + Já está em falta na sua biblioteca + + + + %n possible comic(s) + + %n banda desenhada possível + %n bandas desenhadas possíveis + + + + + Not in library + Fora da biblioteca + + + + CblMatchComicDialog + + + Match to library comic + Associar a uma banda desenhada da biblioteca + + + + Loading the library... + A carregar a biblioteca... + + + + The library cannot be read. + Não é possível ler a biblioteca. + + + + ClassicComicsView + + + Hide comic flow + Ocultar Comic Flow + + + + ComicInfoView + + + Characters + Personagens + + + + Main character or team + Personagem principal ou equipa + + + + Teams + Equipas + + + + Locations + Locais + + + + Authors + Autores + + + + writer + argumentista + + + + penciller + desenhador (lápis) + + + + inker + arte-finalista + + + + colorist + colorista + + + + letterer + letrista + + + + cover artist + artista da capa + + + + editor + editor + + + + imprint + chancela editorial + + + + Publisher + Editora + + + + color + cor + + + + b/w + p/b + + + + ComicManagementCoordinator + + + + YACReader not found + YACReader não encontrado + + + + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. + YACReader não encontrado. O YACReader deve estar instalado na mesma pasta que o YACReaderLibrary. + + + + YACReader not found. There might be a problem with your YACReader installation. + YACReader não encontrado. Pode haver um problema com a instalação do YACReader. + + + + Error + Erro + + + + Error opening comic with third party reader. + Erro ao abrir a banda desenhada com um leitor de terceiros. + + + + ComicModel + + + yes + sim + + + + no + não + + + + Title + Título + + + + File Name + Nome do ficheiro + + + + Pages + Páginas + + + + Size + Tamanho + + + + Read + Lido + + + + Current Page + Página atual + + + + Publication Date + Data de publicação + + + + Rating + Classificação + + + + Series + Série + + + + Volume + Volume + + + + Story Arc + Arco narrativo + + + + ComicVineDialog + + + skip + saltar + + + + back + voltar + + + + next + seguinte + + + + search + pesquisar + + + + close + fechar + + + + + + Looking for volume... + A procurar volume... + + + + + comic %1 of %2 - %3 + banda desenhada %1 de %2 - %3 + + + + %1 comics selected + %1 bandas desenhadas selecionadas + + + + Error connecting to Comic Vine + Erro ao ligar ao Comic Vine + + + + + Retrieving tags for: %1 + A obter etiquetas para: %1 + + + + Creating missing comic %1 of %2 + A criar a banda desenhada em falta %1 de %2 + + + + Comic Vine did not answer for %n comic(s). + + O Comic Vine não respondeu para %n banda desenhada. + O Comic Vine não respondeu para %n bandas desenhadas. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n banda desenhada em falta recebeu apenas os dados básicos do volume. + %n bandas desenhadas em falta receberam apenas os dados básicos do volume. + + + + + %n missing comic(s) have no cover. + + %n banda desenhada em falta não tem capa. + %n bandas desenhadas em falta não têm capa. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + O Comic Vine permite cerca de 200 pedidos por hora; tente novamente mais tarde para as bandas desenhadas não atualizadas. + + + + Try again later for the comics that were not updated. + Tente novamente mais tarde para as bandas desenhadas não atualizadas. + + + + Comic Vine + Comic Vine + + + + Retrieving volume info... + A obter informações do volume... + + + + Looking for comic... + A procurar banda desenhada... + + + + ContinueReadingGridHeader + + + Continue Reading... + Continuar a ler... + + + + CreateLibraryDialog + + + Create new library + Criar nova biblioteca + + + + Cancel + Cancelar + + + + Create + Criar + + + + Comics folder: + Pasta das bandas desenhadas: + + + + Library Name: + Nome da biblioteca: + + + + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + Criar uma biblioteca pode demorar vários minutos. Pode interromper o processo e atualizar a biblioteca mais tarde para concluir a tarefa. + + + + Path not found + Caminho não encontrado + + + + The selected path does not exist or is not a valid path. Be sure that you have write access to this folder + O caminho selecionado não existe ou não é um caminho válido. Certifique-se de que tem acesso de escrita a esta pasta + + + + DBHelper + + + The folder entry could not be found in the library database. + A entrada da pasta não foi encontrada na base de dados da biblioteca. + + + + DelayedProgressDialog + + + Cancel + Cancelar + + + + EditShortcutsDialog + + + Restore defaults + Restaurar predefinições + + + + To change a shortcut, double-click the key combination and type the new keys. + Para alterar um atalho, faça duplo clique na combinação de teclas e introduza as novas teclas. + + + + Shortcuts settings + Definições de atalhos + + + + Shortcut in use + Atalho em utilização + + + + The shortcut "%1" is already assigned to another function + O atalho "%1" já está atribuído a outra função + + + + EmptyFolderWidget + + + This folder doesn't contain comics yet + Esta pasta ainda não contém bandas desenhadas + + + + EmptyInfoView + + + Nothing selected + Nada selecionado + + + + Select a comic or folder to see its information. + Selecione uma banda desenhada ou uma pasta para ver as respetivas informações. + + + + EmptyLabelWidget + + + This label doesn't contain comics yet + Esta etiqueta ainda não contém bandas desenhadas + + + + EmptyListFolderWidget + + + This folder is empty + Esta pasta está vazia + + + + EmptyReadingListWidget + + + This reading list does not contain any comics yet + Esta lista de leitura ainda não contém bandas desenhadas + + + + EmptySpecialListWidget + + + No favorites + Sem favoritos + + + + You are not reading anything yet, come on!! + Ainda não está a ler nada, vá lá!! + + + + There are no recent comics! + Não há bandas desenhadas recentes! + + + + ExportComicsInfoDialog + + + Cancel + Cancelar + + + + Create + Criar + + + + Output file: + Ficheiro de saída: + + + + Export comics info + Exportar informações das bandas desenhadas + + + + Destination database name + Nome da base de dados de destino + + + + Problem found while writing + Problema encontrado durante a escrita + + + + The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder + O caminho selecionado para o ficheiro de saída não existe ou não é um caminho válido. Certifique-se de que tem acesso de escrita a esta pasta + + + + ExportLibraryDialog + + + Cancel + Cancelar + + + + Create + Criar + + + + Output folder: + Pasta de saída: + + + + Create covers package + Criar pacote de capas + + + + Destination directory + Pasta de destino + + + + Problem found while writing + Problema encontrado durante a escrita + + + + The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder + O caminho selecionado para o ficheiro de saída não existe ou não é um caminho válido. Certifique-se de que tem acesso de escrita a esta pasta + + + + FileComic + + + 7z not found + 7z não encontrado + + + + CRC error on page (%1): some of the pages will not be displayed correctly + Erro de CRC na página (%1): algumas páginas não serão apresentadas corretamente + + + + Unknown error opening the file + Erro desconhecido ao abrir o ficheiro + + + + + Format not supported + Formato não suportado + + + + FolderInfoView + + + Unknown + Desconhecido + + + + Items + Itens + + + + Type + Tipo + + + + Reading status + Estado de leitura + + + + Read + Lido + + + + Unread + Não lidos + + + + Collection status + Estado da coleção + + + + Completed + Concluído + + + + In progress + Em curso + + + + Added + Adicionado + + + + Updated + Atualizado + + + + FolderManagementCoordinator + + + Add new folder + Adicionar nova pasta + + + + Folder name: + Nome da pasta: + + + + GridComicsView + + + Show info + Mostrar informações + + + + Manga + Manga + + + + Western manga + Manga ocidental + + + + Web comic + Banda desenhada web + + + + Yonkoma + Yonkoma + + + + Comic + Banda desenhada + + + + Unknown + Desconhecido + + + + HelpAboutDialog + + + About + Acerca de + + + + Help + Ajuda + + + + System info + Informações do sistema + + + + Changelog + Registo de alterações + + + + ImportComicsInfoDialog + + + Cancel + Cancelar + + + + Import comics info + Importar informações das bandas desenhadas + + + + Info database location: + Localização da base de dados de informações: + + + + Import + Importar + + + + Comics info file (*.ydb) + Ficheiro de informações das bandas desenhadas (*.ydb) + + + + ImportLibraryDialog + + + Destination folder: + Pasta de destino: + + + + Cancel + Cancelar + + + + Unpack + Desempacotar + + + + Compressed library covers (*.clc) + Capas de biblioteca comprimidas (*.clc) + + + + Package location: + Localização do pacote: + + + + Library Name: + Nome da biblioteca: + + + + Extract a catalog + Extrair um catálogo + + + + ImportWidget + + + stop + parar + + + + Some of the comics being added... + Algumas das bandas desenhadas estão a ser adicionadas... + + + + Importing comics + A importar bandas desenhadas + + + + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>O YACReaderLibrary está a criar uma nova biblioteca.</p><p>Criar uma biblioteca pode demorar vários minutos. Pode interromper o processo e atualizar a biblioteca mais tarde para concluir a tarefa.</p> + + + + Updating the library + A atualizar a biblioteca + + + + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>A biblioteca atual está a ser atualizada. Para atualizações mais rápidas, atualize as suas bibliotecas com frequência.</p><p>Pode interromper o processo e continuar a atualizar esta biblioteca mais tarde.</p> + + + + Upgrading the library + A atualizar a versão da biblioteca + + + + <p>The current library is being upgraded, please wait.</p> + <p>A versão da biblioteca atual está a ser atualizada. Aguarde.</p> + + + + Scanning the library + A analisar a biblioteca + + + + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>A biblioteca atual está a ser analisada à procura de informações de metadados XML antigos.</p><p>Isto só é necessário uma vez e apenas se a biblioteca tiver sido criada com o YACReaderLibrary 9.8.2 ou anterior.</p> + + + + <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> + <p>A biblioteca atual está a ser verificada à procura de capas em falta e de informações incompletas das bandas desenhadas.</p><p>Isto pode demorar vários minutos. Pode interromper o processo e executá-lo novamente mais tarde.</p> + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Não foi possível criar a pasta de dados da biblioteca: %1. Verifique o acesso à pasta e o espaço disponível em disco. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + A pasta de dados da biblioteca está em falta: %1. Restaure-a ou recrie a biblioteca antes de atualizar. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + A base de dados da biblioteca está em falta: %1. Restaure uma cópia de segurança ou recrie a biblioteca antes de atualizar. + + + + + Unable to start a library database transaction: %1 + Não foi possível iniciar uma transação na base de dados da biblioteca: %1 + + + + + Unable to commit the library database: %1 + Não foi possível confirmar a transação na base de dados da biblioteca: %1 + + + + LibraryInfoView + + + Library info + Informações da biblioteca + + + + Number of folders + Número de pastas + + + + Number of comics + Número de bandas desenhadas + + + + Number of read comics + Número de bandas desenhadas lidas + + + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Esta biblioteca está ocupada. Aguarde o fim da manutenção. Se parecer bloqueada, feche a aplicação indicada em Detalhes e tente novamente. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Uma operação de manutenção anterior pode não ter libertado esta biblioteca. Antes de recuperar o acesso, confirme que nenhuma operação do YACReaderLibrary ou YACReaderLibraryServer a está a utilizar, incluindo noutro computador. + + + + Retry + Tentar novamente + + + + Recover and retry + Recuperar e tentar novamente + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + O proprietário do bloqueio da biblioteca mudou. Repita a operação e verifique o novo proprietário antes de recuperar o acesso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Não foi possível remover o bloqueio de manutenção: %1. Confirme que o ficheiro não é só de leitura e que pode escrever na pasta de dados da biblioteca, e tente novamente. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Uma pasta ocupa o caminho do ficheiro de bloqueio de manutenção: %1. Mude o nome ou mova essa pasta antes de tentar novamente. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Não foi possível criar o ficheiro de bloqueio da biblioteca: %1. Verifique as permissões de escrita da pasta de dados da biblioteca. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + A pasta de dados da biblioteca está em falta: %1. Restaure a pasta ou recrie a biblioteca antes de a atualizar. + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Não foi possível criar o ficheiro de bloqueio da biblioteca: %1. Verifique o espaço disponível em disco e o acesso à pasta de dados da biblioteca. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + A biblioteca tem um bloqueio de manutenção cujo proprietário não pôde ser verificado (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Outra operação de manutenção está a utilizar esta biblioteca (%1)%2 + + + + LibraryManagementCoordinator + + + Error opening the library + Erro ao abrir a biblioteca + + + + Error creating the library + Erro ao criar a biblioteca + + + + Error updating the library + Erro ao atualizar a biblioteca + + + + + XML information rescan + Nova análise de informações XML + + + + Restore recovery + Recuperação do restauro + + + + Library upgrade + Atualização da biblioteca + + + + Library maintenance + Manutenção da biblioteca + + + + Recover library access + Recuperar acesso à biblioteca + + + + LibraryWindow + + + Do you want to remove the library '%1'? + Pretende remover a biblioteca '%1'? + + + + YACReader Library + Biblioteca YACReader + + + + Are you sure? + Tem a certeza? + + + + + Delete folder + Eliminar pasta + + + + Upgrade failed + Falha na atualização de versão + + + + There were errors during library upgrade in: + Ocorreram erros durante a atualização da versão da biblioteca em: + + + + Restore recovery failed + Falha na recuperação da restauração + + + + Update needed + Atualização necessária + + + + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? + Esta biblioteca foi criada com uma versão anterior do YACReaderLibrary. Tem de ser atualizada. Atualizar agora? + + + + Download new version + Transferir nova versão + + + + This library was created with a newer version of YACReaderLibrary. Download the new version now? + Esta biblioteca foi criada com uma versão mais recente do YACReaderLibrary. Transferir agora a nova versão? + + + + Library not available + Biblioteca indisponível + + + + Library '%1' is no longer available. Do you want to remove it? + A biblioteca '%1' já não está disponível. Pretende removê-la? + + + + Old library + Biblioteca antiga + + + + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? + A biblioteca '%1' foi criada com uma versão mais antiga do YACReaderLibrary. Tem de ser criada novamente. Pretende criar a biblioteca agora? + + + + Folder name: + Nome da pasta: + + + + + + No folder selected + Nenhuma pasta selecionada + + + + + + Please select a folder first + Selecione primeiro uma pasta + + + + Error in path + Erro no caminho + + + + There was an error accessing the folder's path + Ocorreu um erro ao aceder ao caminho da pasta + + + + The selected folder and all its contents will be deleted from your disk. Are you sure? + A pasta selecionada e todo o seu conteúdo serão eliminados do seu disco. Tem a certeza? + + + + + Unable to delete + Não foi possível eliminar + + + + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. + Ocorreu um problema ao tentar eliminar as pastas selecionadas. Verifique as permissões de escrita e certifique-se de que nenhuma aplicação está a utilizar estas pastas ou qualquer um dos ficheiros nelas contidos. + + + + Edit smart list... + Editar lista inteligente... + + + + Delete smart list + Eliminar lista inteligente + + + + + + New folder... + Nova pasta... + + + + + + Move to folder... + Mover para pasta... + + + + Rename folder... + Mudar o nome da pasta... + + + + Match to library comics... + Associar às bandas desenhadas da biblioteca... + + + + Track missing issues... + Registar números em falta... + + + + Export reading list... + Exportar lista de leitura... + + + + Rename or organize files + Mudar o nome ou organizar ficheiros + + + + Set the type of the selected comics + Definir o tipo das bandas desenhadas selecionadas + + + + Save as smart list… + Guardar como lista inteligente… + + + + Search filters + Filtros de pesquisa + + + + Unread + Não lidos + + + + In progress + Em curso + + + + Highly rated + Muito bem avaliadas + + + + Recently added + Adicionadas recentemente + + + + Search syntax… + Sintaxe de pesquisa… + + + + Package operation failed + Falha na operação de pacote + + + + The covers package operation could not be completed. + Não foi possível concluir a operação com o pacote de capas. + + + + Rename folder + Mudar o nome da pasta + + + + Invalid folder name + Nome de pasta inválido + + + + The folder name is empty or contains characters that are not supported. + O nome da pasta está vazio ou contém carateres não suportados. + + + + + + Unable to rename folder + Não foi possível mudar o nome da pasta + + + + A file or folder named '%1' already exists. + Já existe um ficheiro ou uma pasta com o nome '%1'. + + + + The folder could not be renamed on disk. Please check the folder name and write permissions. + +Folder: %1 + Não foi possível mudar o nome da pasta no disco. Verifique o nome da pasta e as permissões de escrita. + +Pasta: %1 + + + + The library database could not be updated. The folder rename on disk was reverted. + Não foi possível atualizar a base de dados da biblioteca. A mudança de nome da pasta no disco foi revertida. + + + + The library database could not be updated, and the folder rename on disk could not be reverted. The library now needs to be updated manually. + Não foi possível atualizar a base de dados da biblioteca, nem reverter a mudança de nome da pasta no disco. Agora, é necessário atualizar manualmente a biblioteca. + + + + Save covers + Guardar capas + + + + You are adding too many libraries. + Está a adicionar demasiadas bibliotecas. + + + + You are adding too many libraries. + +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. + +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. + Está a adicionar demasiadas bibliotecas. + +Provavelmente só precisa de uma biblioteca na pasta de bandas desenhadas de nível superior; pode navegar por quaisquer subpastas através da secção de pastas na barra lateral esquerda. + +O YACReaderLibrary não o impedirá de criar mais bibliotecas, mas deve manter o número de bibliotecas reduzido. + + + + Library not found + Biblioteca não encontrada + + + + The selected folder doesn't contain any library. + A pasta selecionada não contém nenhuma biblioteca. + + + + + YACReader library database (*.ydb) + Base de dados da biblioteca do YACReader (*.ydb) + + + + The library database backup was created at: +%1 + A cópia de segurança da base de dados da biblioteca foi criada em: +%1 + + + + Unable to create the library database backup: +%1 + Não foi possível criar a cópia de segurança da base de dados da biblioteca: +%1 + + + + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? + Feche o YACReaderLibraryServer e qualquer outra aplicação YACReader que esteja a utilizar esta biblioteca antes de restaurar. Continuar? + + + + Restoring library database... + A restaurar a base de dados da biblioteca... + + + + The current library database is invalid. Restore the selected backup anyway? + A base de dados atual da biblioteca é inválida. Restaurar mesmo assim a cópia de segurança selecionada? + + + + + +Restart YACReaderLibrary before attempting recovery again. + + +Reinicie o YACReaderLibrary antes de tentar novamente a recuperação. + + + + The library database was restored successfully. Update the library now? + A base de dados da biblioteca foi restaurada com êxito. Atualizar agora a biblioteca? + + + + Library database damaged + Base de dados da biblioteca danificada + + + + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. + A base de dados da biblioteca '%1' está danificada, pelo que as atualizações, a manutenção e as cópias de segurança normais não estão disponíveis. O YACReader pode tentar reparar a base de dados. Alguns dados danificados poderão não ser recuperáveis. As cópias de segurança existentes não serão alteradas. + + + + Attempt repair + Tentar reparar + + + + Restore a backup... + Restaurar uma cópia de segurança... + + + + Repairing library database... + A reparar a base de dados da biblioteca... + + + + + + + Library database repair + Reparação da base de dados da biblioteca + + + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Verificar a base de dados da biblioteca '%1' e tentar repará-la se necessário? As cópias de segurança existentes não serão alteradas. + + + + Check and repair + Verificar e reparar + + + + The library database is already valid. + A base de dados da biblioteca já é válida. + + + + Library database repaired + Base de dados da biblioteca reparada + + + + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: +%1 + A base de dados da biblioteca foi reparada através da reconstrução dos respetivos índices. O original danificado foi preservado em: +%1 + + + + Library database rebuilt + Base de dados da biblioteca reconstruída + + + + The library database was rebuilt successfully. The damaged original was preserved at: +%1 + +Update the library now? + A base de dados da biblioteca foi reconstruída com êxito. O original danificado foi preservado em: +%1 + +Atualizar agora a biblioteca? + + + + + +The damaged original was preserved at: +%1 + + +O original danificado foi preservado em: +%1 + + + + Library database repair failed + Falha ao reparar a base de dados da biblioteca + + + + The library database could not be repaired: +%1%2 + +You can restore a backup from the Library menu or recreate the library. + Não foi possível reparar a base de dados da biblioteca: +%1%2 + +Pode restaurar uma cópia de segurança a partir do menu Biblioteca ou recriar a biblioteca. + + + + Remove and delete metadata and backups + Remover e eliminar metadados e cópias de segurança + + + + Library info + Informações da biblioteca + + + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + Ocorreu um problema ao tentar eliminar as bandas desenhadas selecionadas. Verifique as permissões de escrita nos ficheiros selecionados ou na pasta que os contém. + + + + Assign comics numbers + Atribuir números às bandas desenhadas + + + + Assign numbers starting at: + Atribuir números a partir de: + + + + + Invalid image + Imagem inválida + + + + + The selected file is not a valid image. + O ficheiro selecionado não é uma imagem válida. + + + + + + Error saving cover + Erro ao guardar a capa + + + + + + There was an error saving the cover image. + Ocorreu um erro ao guardar a imagem da capa. + + + + + Match to library comic + Associar a uma banda desenhada da biblioteca + + + + This also gives the comic to entries in %n other list(s): %1 + + Isto também associa a banda desenhada a entradas de %n outra lista: %1 + Isto também associa a banda desenhada a entradas de %n outras listas: %1 + + + + + The comic could not be matched. Nothing was changed. + Não foi possível associar a banda desenhada. Nada foi alterado. + + + + The selected missing comics will be removed from this folder. Are you sure? + As bandas desenhadas em falta selecionadas serão removidas desta pasta. Tem a certeza? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + As bandas desenhadas selecionadas serão eliminadas do disco, e as bandas desenhadas em falta selecionadas serão removidas desta pasta. Tem a certeza? + + + + Delete comics + Eliminar bandas desenhadas + + + + All the selected comics will be deleted from your disk. Are you sure? + Todas as bandas desenhadas selecionadas serão eliminadas do seu disco. Tem a certeza? + + + + Remove the comics from Reading? Their reading progress will be reset. + Remover as bandas desenhadas de Em leitura? O progresso de leitura será reposto. + + + + The comics will be removed from Favorites. Are you sure? + As bandas desenhadas serão removidas dos Favoritos. Tem a certeza? + + + + Remove comics + Remover bandas desenhadas + + + + Comics will only be deleted from the current label/list. Are you sure? + As bandas desenhadas só serão eliminadas da etiqueta/lista atual. Tem a certeza? + + + + Library name already exists + O nome da biblioteca já existe + + + + There is another library with the name '%1'. + Existe outra biblioteca com o nome '%1'. + + + + Repaired: %1 +Failed: %2 +Missing files: %3 + Reparados: %1 +Falhados: %2 +Ficheiros em falta: %3 + + + + Copying comics... + A copiar bandas desenhadas... + + + + Moving comics... + A mover bandas desenhadas... + + + + LibraryWindowActions + + + Create a new library + Criar uma nova biblioteca + + + + Open an existing library + Abrir uma biblioteca existente + + + + + Export comics info + Exportar informações das bandas desenhadas + + + + + Import comics info + Importar informações das bandas desenhadas + + + + Pack covers + Empacotar capas + + + + Pack the covers of the selected library + Empacotar as capas da biblioteca selecionada + + + + Unpack covers + Desempacotar capas + + + + Unpack a catalog + Desempacotar um catálogo + + + + Update library + Atualizar biblioteca + + + + Update current library + Atualizar biblioteca atual + + + + Back up library database + Criar cópia de segurança da base de dados da biblioteca + + + + Create a backup of the current library database + Criar uma cópia de segurança da base de dados atual da biblioteca + + + + Restore library database backup + Restaurar cópia de segurança da base de dados da biblioteca + + + + Restore the current library database from a backup + Restaurar a base de dados atual da biblioteca a partir de uma cópia de segurança + + + + Repair library database... + Reparar base de dados da biblioteca... + + + + Check the current library database and attempt to repair any damage + Verificar a base de dados da biblioteca atual e tentar reparar danos + + + + Recover library access... + Recuperar acesso à biblioteca... + + + + Recover access after an interrupted library maintenance operation + Recuperar o acesso após uma operação de manutenção da biblioteca interrompida + + + + + Repair covers and comic info + Reparar capas e informações das bandas desenhadas + + + + Retry comics with missing covers or incomplete information + Voltar a processar bandas desenhadas com capas em falta ou informações incompletas + + + + Rename library + Mudar o nome da biblioteca + + + + Rename current library + Mudar o nome da biblioteca atual + + + + Remove library + Remover biblioteca + + + + Remove current library from your collection + Remover a biblioteca atual da sua coleção + + + + Rescan library for XML info + Voltar a analisar a biblioteca para obter informações XML + + + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. + Tenta encontrar informações XML incorporadas nos ficheiros de banda desenhada. Só precisa de o fazer se a biblioteca tiver sido criada com a versão 9.8.2 ou anterior ou se estiver a utilizar software de terceiros para incorporar informações XML nos ficheiros. + + + + Open library folder... + Abrir pasta da biblioteca... + + + + Open the root folder of the current library + Abrir a pasta raiz da biblioteca atual + + + + Show library info + Mostrar informações da biblioteca + + + + Show information about the current library + Mostrar informações sobre a biblioteca atual + + + + Open current comic + Abrir banda desenhada atual + + + + Open current comic in YACReader + Abrir a banda desenhada atual no YACReader + + + + Save selected covers to... + Guardar capas selecionadas em... + + + + Save covers of the selected comics as JPG files + Guardar as capas das bandas desenhadas selecionadas como ficheiros JPG + + + + + Set as read + Marcar como lido + + + + Set comic as read + Marcar banda desenhada como lida + + + + + Set as unread + Marcar como não lido + + + + Set comic as unread + Marcar banda desenhada como não lida + + + + + manga + manga + + + + Set issue as manga + Definir edição como manga + + + + + comic + banda desenhada + + + + Set issue as normal + Definir edição como normal + + + + western manga + manga ocidental + + + + Set issue as western manga + Definir edição como manga ocidental + + + + + web comic + banda desenhada web + + + + Set issue as web comic + Definir edição como banda desenhada web + + + + + yonkoma + yonkoma + + + + Set issue as yonkoma + Definir edição como yonkoma + + + + Show/Hide marks + Mostrar/ocultar marcas + + + + Show or hide read marks + Mostrar ou ocultar marcas de leitura + + + + Show/Hide recent indicator + Mostrar/ocultar indicador de recentes + + + + Show or hide recent indicator + Mostrar ou ocultar indicador de recentes + + + + + Fullscreen mode on/off + Modo de ecrã inteiro ativado/desativado + + + + Help, About YACReader + Ajuda, Acerca do YACReader + + + + Add new folder + Adicionar nova pasta + + + + Add new folder to the current library + Adicionar nova pasta à biblioteca atual + + + + Rename folder + Mudar o nome da pasta + + + + Rename the current folder on disk and in the library + Mudar o nome da pasta atual no disco e na biblioteca + + + + Delete folder + Eliminar pasta + + + + Delete current folder from disk + Eliminar a pasta atual do disco + + + + Select root node + Selecionar nó raiz + + + + Expand all nodes + Expandir todos os nós + + + + Collapse all nodes + Recolher todos os nós + + + + Show options dialog + Mostrar caixa de diálogo de opções + + + + Show comics server options dialog + Mostrar caixa de diálogo de opções do servidor de bandas desenhadas + + + + + Change between comics views + Alternar entre vistas de bandas desenhadas + + + + Grid view + Vista em grelha + + + + Flow view + + + + + Info view + + + + + Open folder... + Abrir pasta... + + + + + Rename files... + Mudar o nome dos ficheiros... + + + + + Organize into folders... + Organizar em pastas... + + + + Set as uncompleted + Definir como incompleto + + + + Set as completed + Definir como concluído + + + + Set custom cover + Definir capa personalizada + + + + Delete custom cover + Eliminar capa personalizada + + + + western manga (left to right) + manga ocidental (da esquerda para a direita) + + + + Open containing folder... + Abrir a pasta que contém o ficheiro... + + + + Select all comics + Selecionar todas as bandas desenhadas + + + + Edit + Editar + + + + Assign current order to comics + Atribuir a ordem atual às bandas desenhadas + + + + Update cover + Atualizar capa + + + + Delete selected comics + Eliminar bandas desenhadas selecionadas + + + + Delete metadata from selected comics + Eliminar metadados das bandas desenhadas selecionadas + + + + Download tags from Comic Vine + Transferir etiquetas do Comic Vine + + + + Focus search line + Focar o campo de pesquisa + + + + Focus comics view + Focar a vista de bandas desenhadas + + + + Edit shortcuts + Editar atalhos + + + + &Quit + &Sair + + + + Update folder + Atualizar pasta + + + + Update current folder + Atualizar pasta atual + + + + Scan legacy XML metadata + Analisar metadados XML antigos + + + + New reading list + Nova lista de leitura + + + + Add a new reading list to the current library + Adicionar uma nova lista de leitura à biblioteca atual + + + + New smart list... + Nova lista inteligente... + + + + Add a saved search to the current library + Adicionar uma pesquisa guardada à biblioteca atual + + + + New folder... + Nova pasta... + + + + Add a folder for reading lists or smart lists + Adicionar uma pasta para listas de leitura ou listas inteligentes + + + + Import reading lists... + Importar listas de leitura... + + + + Import CBL reading lists into the current library + Importar listas de leitura CBL para a biblioteca atual + + + + New + Novo + + + + Add or import reading lists + Adicionar ou importar listas de leitura + + + + Remove reading list + Remover lista de leitura + + + + Remove current reading list from the library + Remover a lista de leitura atual da biblioteca + + + + Add new label + Adicionar nova etiqueta + + + + Add a new label to this library + Adicionar uma nova etiqueta a esta biblioteca + + + + Rename selected list + Mudar o nome da lista selecionada + + + + Rename any selected labels or lists + Mudar o nome de quaisquer etiquetas ou listas selecionadas + + + + Add to... + Adicionar a... + + + + Favorites + Favoritos + + + + Add selected comics to favorites list + Adicionar bandas desenhadas selecionadas à lista de favoritos + + + + + Reset rating + Repor classificação + + + + LibraryWindowMenus + + + comic + banda desenhada + + + + manga + manga + + + + western manga (left to right) + manga ocidental (da esquerda para a direita) + + + + web comic + banda desenhada web + + + + 4koma (top to bottom) + 4koma (de cima para baixo) + + + + + + + Set type + Definir tipo + + + + Library + Biblioteca + + + + Folder + Pasta + + + + Comic + Banda desenhada + + + + Match to library comic... + Associar a uma banda desenhada da biblioteca... + + + + + + + Track missing issues... + Registar números em falta... + + + + Open folder... + Abrir pasta... + + + + Update folder + Atualizar pasta + + + + Rename folder + Mudar o nome da pasta + + + + Rename files... + Mudar o nome dos ficheiros... + + + + Organize into folders... + Organizar em pastas... + + + + Rescan library for XML info + Voltar a analisar a biblioteca para obter informações XML + + + + Set as uncompleted + Definir como incompleto + + + + Set as completed + Definir como concluído + + + + Set as read + Marcar como lido + + + + + Set as unread + Marcar como não lido + + + + Set custom cover + Definir capa personalizada + + + + + Delete custom cover + Eliminar capa personalizada + + + + ListInfoView + + + 1 comic + 1 banda desenhada + + + + %1 comics + %1 bandas desenhadas + + + + Last day + Último dia + + + + Last %1 days + Últimos %1 dias + + + + 1 sublist + 1 sublista + + + + %1 sublists + %1 sublistas + + + + LocalComicListModel + + + file name + nome do ficheiro + + + + MoveToFolderDialog + + + Move to folder + Mover para pasta + + + + Top level + Nível superior + + + + Move "%1" to: + Mover "%1" para: + + + + NewListFolderDialog + + + New folder + Nova pasta + + + + Reading lists + Listas de leitura + + + + Smart lists + Listas inteligentes + + + + Folder name: + Nome da pasta: + + + + For: + Para: + + + + NoLibrariesWidget + + + You don't have any libraries yet + Ainda não tem nenhuma biblioteca + + + + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Pode criar uma biblioteca em qualquer pasta; o YACReaderLibrary importará todas as bandas desenhadas e pastas dessa pasta. Se já criou alguma biblioteca anteriormente, pode abri-la.</p><p>Não se esqueça de que pode utilizar o YACReader como aplicação autónoma para ler as bandas desenhadas no seu computador.</p> + + + + create your first library + criar a sua primeira biblioteca + + + + add an existing one + adicionar uma existente + + + + NoSearchResultsWidget + + + No results + Sem resultados + + + + OptionsDialog + + + Language + Idioma + + + + Application language + Idioma da aplicação + + + + System default + Predefinição do sistema + + + + Tray icon settings (experimental) + Definições do ícone da área de notificação (experimental) + + + + Close to tray + Fechar para a área de notificação + + + + Start in the system tray + Iniciar na área de notificação do sistema + + + + Edit Comic Vine API key + Editar chave de API do Comic Vine + + + + Comic Vine API key + Chave de API do Comic Vine + + + + ComicInfo.xml legacy support + Suporte legado para ComicInfo.xml + + + + Import metadata from ComicInfo.xml when adding new comics + Importar metadados de ComicInfo.xml ao adicionar novas bandas desenhadas + + + + Consider 'recent' items added or updated since X days ago + Considerar como 'recentes' os itens adicionados ou atualizados há X dias + + + + Third party reader + Leitor de terceiros + + + + Write {comic_file_path} where the path should go in the command + Escreva {comic_file_path} onde o caminho deve ser colocado no comando + + + + Clear + Limpar + + + + Update libraries at startup + Atualizar bibliotecas no arranque + + + + Try to detect changes automatically + Tentar detetar alterações automaticamente + + + + Update libraries periodically + Atualizar bibliotecas periodicamente + + + + Interval: + Intervalo: + + + + 30 minutes + 30 minutos + + + + 1 hour + 1 hora + + + + 2 hours + 2 horas + + + + 4 hours + 4 horas + + + + 8 hours + 8 horas + + + + 12 hours + 12 horas + + + + daily + diariamente + + + + Update libraries at certain time + Atualizar bibliotecas a determinada hora + + + + Time: + Hora: + + + + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. + AVISO! Durante as atualizações da biblioteca, a escrita na base de dados está desativada! +Não agende atualizações enquanto estiver a utilizar ativamente a aplicação. +Durante as atualizações automáticas, a aplicação bloqueará algumas ações até a atualização terminar. +Para interromper uma atualização automática, toque no indicador de carregamento junto ao título Bibliotecas. + + + + Modifications detection + Deteção de modificações + + + + Compare the modified date of files when updating a library (not recommended) + Comparar a data de modificação dos ficheiros ao atualizar uma biblioteca (não recomendado) + + + + Enable background image + Ativar imagem de fundo + + + + Opacity level + Nível de opacidade + + + + Blur level + Nível de desfoque + + + + Use selected comic cover as background + Usar a capa da banda desenhada selecionada como fundo + + + + Restore defaults + Restaurar predefinições + + + + Background + Fundo + + + + Display continue reading banner + Mostrar a faixa "Continuar a ler" + + + + Display current comic banner + Mostrar a faixa da banda desenhada atual + + + + Continue reading + Continuar a ler + + + + Mix folders and comics + Misturar pastas e bandas desenhadas + + + + Start comics on a new row + Iniciar as bandas desenhadas numa nova linha + + + + Content + Conteúdo + + + + Comic Flow + Comic Flow + + + + + Libraries + Bibliotecas + + + + Grid view + Vista em grelha + + + + General + Geral + + + + Appearance + Aspeto + + + + Options + Opções + + + + Restart is needed + É necessário reiniciar + + + + OrganizeFiles + + + + Unknown Series + Série desconhecida + + + + series + série + + + + Unknown Publisher + Editora desconhecida + + + + publisher + editora + + + + title + título + + + + Unknown Comic + Banda desenhada desconhecida + + + + Series #Number - Title + Série #Número - Título + + + + Series #Number + Série #Número + + + + Series #Number (of Count) + Série #Número (de Total) + + + + Number - Title + Número - Título + + + + Series #Number (Year) + Série #Número (Ano) + + + + Publisher / Series / Series #Number - Title + Editora / Série / Série #Número - Título + + + + Publisher / Imprint / Series / Series #Number - Title + Editora / Chancela / Série / Série #Número - Título + + + + Series / Series #Number + Série / Série #Número + + + + Publisher / Series / Series #Number (Year) + Editora / Série / Série #Número (Ano) + + + + Series / original file name + Série / nome original do ficheiro + + + + Renamed, %1 is already in use + Nome alterado, %1 já está em utilização + + + + Missing metadata: %1 + Metadados em falta: %1 + + + + %1 could not be created + Não foi possível criar %1 + + + + %1 (copy also failed: %2) + %1 (a cópia também falhou: %2) + + + + the copy did not have the same size as the original + a cópia não tinha o mesmo tamanho do original + + + + the original could not be deleted after it was copied + não foi possível eliminar o original após a cópia + + + + The destination folder could not be created. + Não foi possível criar a pasta de destino. + + + + %1 (the file was left at %2) + %1 (o ficheiro ficou em %2) + + + + OrganizeFilesCoordinator + + + + Organize files + Organizar ficheiros + + + + This folder does not contain any comics. + Esta pasta não contém nenhuma banda desenhada. + + + + the library database could not be opened + não foi possível abrir a base de dados da biblioteca + + + + the library database could not be locked for writing + não foi possível bloquear a base de dados da biblioteca para escrita + + + + a folder entry could not be restored + não foi possível restaurar uma entrada de pasta + + + + a comic entry could not be updated + não foi possível atualizar uma entrada de banda desenhada + + + + the library database could not be saved: %1 + não foi possível guardar a base de dados da biblioteca: %1 + + + + the record of the last organize run could not be read + não foi possível ler o registo da última organização + + + + the folder %1 could not be created + não foi possível criar a pasta %1 + + + + %n file(s) could not be moved back + + não foi possível mover %n ficheiro de volta + não foi possível mover %n ficheiros de volta + + + + + OrganizeFilesDialog + + + Organize files + Organizar ficheiros + + + + + Rename files + Mudar o nome dos ficheiros + + + + Preparing the preview... + A preparar a pré-visualização... + + + + &Filename format: + &Formato do nome do ficheiro: + + + + &Path format: + Formato do &caminho: + + + + Filename format + Formato do nome do ficheiro + + + + Path format + Formato do caminho + + + + Presets + Predefinições + + + + Insert + Inserir + + + + Optional part < > + Parte opcional < > + + + + Disappears completely when the fields inside it are empty. + Desaparece por completo quando os campos no seu interior estão vazios. + + + + Padded number {number:000} + Número com zeros à esquerda {number:000} + + + + Format help... + Ajuda sobre o formato... + + + + selected folder + pasta selecionada + + + + library root + raiz da biblioteca + + + + Move into + Mover para + + + + Reset changes + Repor alterações + + + + Remove selected + Remover selecionados + + + + Show unchanged + Mostrar inalterados + + + + New name + Novo nome + + + + Renamed from + Nome anterior + + + + New location + Nova localização + + + + Moved from + Localização anterior + + + + Remove from list + Remover da lista + + + + Move files + Mover ficheiros + + + + Cancel + Cancelar + + + + Undo + Anular + + + + Copy failure details + Copiar detalhes das falhas + + + + Finish + Concluir + + + + Remove preset + Remover predefinição + + + + Save current format as preset... + Guardar o formato atual como predefinição... + + + + Reset to default format + Repor o formato predefinido + + + + Save preset + Guardar predefinição + + + + Preset name: + Nome da predefinição: + + + + A filename format cannot contain "/". Use Organize files to move comics into folders. + Um formato de nome de ficheiro não pode conter "/". Utilize Organizar ficheiros para mover bandas desenhadas para pastas. + + + + This format cannot be used: %1 + Este formato não pode ser utilizado: %1 + + + + new folder + nova pasta + + + + This folder does not exist yet. It will be created. + Esta pasta ainda não existe. Será criada. + + + + file not found + ficheiro não encontrado + + + + This comic is in the library but not on disk. It is skipped. + Esta banda desenhada está na biblioteca, mas não está no disco. Será ignorada. + + + + name in use + nome em utilização + + + + no metadata + sem metadados + + + + already here + já aqui + + + + This file is already in the right place. + Este ficheiro já está no local certo. + + + + edited + editado + + + + %n will be renamed + + %n será renomeado + %n serão renomeados + + + + + %n will move + + %n será movido + %n serão movidos + + + + + %n unchanged + + %n inalterado + %n inalterados + + + + + %n renamed + + %n renomeado + %n renomeados + + + + + %n removed + + %n removido + %n removidos + + + + + %n missing + + %n em falta + %n em falta + + + + + %n new folder(s) + + %n nova pasta + %n novas pastas + + + + + %n manual change(s) kept + + %n alteração manual mantida + %n alterações manuais mantidas + + + + + Nothing would be renamed with this format. + Com este formato, nada seria renomeado. + + + + Nothing would move with this format. + Com este formato, nada seria movido. + + + + %n file(s) will be renamed. The folders do not change. You can undo it afterwards. + + %n ficheiro será renomeado. As pastas não se alteram. Pode anular depois. + %n ficheiros serão renomeados. As pastas não se alteram. Pode anular depois. + + + + + %n file(s) will move into %1. This changes your files on disk. You can undo it afterwards. + + %n ficheiro será movido para %1. Isto altera os seus ficheiros no disco. Pode anular depois. + %n ficheiros serão movidos para %1. Isto altera os seus ficheiros no disco. Pode anular depois. + + + + + Moving %1 of %2 +%3 + A mover %1 de %2 +%3 + + + + Updating the library... + A atualizar a biblioteca... + + + + Restored name + Nome restaurado + + + + + Moved back from + Movido de volta de + + + + + + + Status + Estado + + + + Final name + Nome final + + + + Previous name + Nome anterior + + + + Restored location + Localização restaurada + + + + Final location + Localização final + + + + Previous location + Localização anterior + + + + Restored + Restaurado + + + + Renamed + Renomeado + + + + Moved + Movido + + + + Undo failed: %1 + Falha ao anular: %1 + + + + Failed: %1 + Falha: %1 + + + + Nothing was moved. + Nada foi movido. + + + + The record this run could be undone from could not be written, so the run did not start: %1 + Não foi possível escrever o registo a partir do qual esta execução poderia ser anulada, pelo que a execução não foi iniciada: %1 + + + + %n file(s) renamed. + + %n ficheiro renomeado. + %n ficheiros renomeados. + + + + + %n file(s) moved into %1. + + %n ficheiro movido para %1. + %n ficheiros movidos para %1. + + + + + The record of this run stopped early, so the run stopped with it: %1 + O registo desta execução parou antes do fim, pelo que a execução parou com ele: %1 + + + + %n file(s) were not moved. + + %n ficheiro não foi movido. + %n ficheiros não foram movidos. + + + + + The library database could not be updated: %1 + Não foi possível atualizar a base de dados da biblioteca: %1 + + + + Use Undo to move the files back, or update the library to make it match the files. + Utilize Anular para mover os ficheiros de volta ou atualize a biblioteca para que corresponda aos ficheiros. + + + + %n empty folder(s) were removed. + + %n pasta vazia foi removida. + %n pastas vazias foram removidas. + + + + + %n file(s) could not be moved. + + Não foi possível mover %n ficheiro. + Não foi possível mover %n ficheiros. + + + + + Moving the files back... + A mover os ficheiros de volta... + + + + Moving back %1 of %2 +%3 + A mover de volta %1 de %2 +%3 + + + + Everything was moved back. + Tudo foi movido de volta. + + + + The undo did not finish: %1 + A anulação não terminou: %1 + + + + Format help + Ajuda sobre o formato + + + + Fields + Campos + + + + Every field is written between braces and is replaced by the metadata of the comic. The Insert menu lists all of them. + Cada campo é escrito entre chavetas e é substituído pelos metadados da banda desenhada. O menu Inserir lista todos eles. + + + + {series} gives %1 + {series} resulta em %1 + + + + Optional parts + Partes opcionais + + + + A part written between the signs < and > disappears completely when every field inside it is empty. Use it for punctuation that belongs to a field, such as brackets or a leading number sign. Text at the start or the end of a name is trimmed without it. + Uma parte escrita entre os sinais < e > desaparece por completo quando todos os campos no seu interior estão vazios. Utilize-a para a pontuação que pertence a um campo, como parênteses ou um cardinal inicial. O texto no início ou no fim de um nome é aparado mesmo sem ela. + + + + {series} ({year}) with no year gives %1 + {series} ({year}) sem ano resulta em %1 + + + + {series}< ({year})> with no year gives %1 + {series}< ({year})> sem ano resulta em %1 + + + + Numbers + Números + + + + Write a colon and some zeros to pad the issue number. This keeps the issues in order in a file browser. + Escreva dois pontos e alguns zeros para preencher o número da edição. Assim, as edições ficam ordenadas num gestor de ficheiros. + + + + + Folders + Pastas + + + + A filename format cannot contain a slash. Every comic keeps its current folder. Use Organize into folders to move comics. + Um formato de nome de ficheiro não pode conter uma barra. Cada banda desenhada mantém a sua pasta atual. Utilize Organizar em pastas para mover bandas desenhadas. + + + + Each part separated by a slash becomes a folder. The last part becomes the file name. The original extension is always kept. + Cada parte separada por uma barra torna-se uma pasta. A última parte torna-se o nome do ficheiro. A extensão original é sempre mantida. + + + + PropertiesDialog + + + General info + Informações gerais + + + + Authors + Autores + + + + Publishing + Publicação + + + + Plot + Enredo + + + + Notes + Notas + + + + Cover page + Página da capa + + + + Load previous page as cover + Carregar página anterior como capa + + + + Load next page as cover + Carregar página seguinte como capa + + + + Reset cover to the default image + Repor a capa para a imagem predefinida + + + + Load custom cover image + Carregar imagem de capa personalizada + + + + Series: + Série: + + + + Title: + Título: + + + + e.g. 1-5, 8, 0.5, Annual 1 + p. ex. 1-5, 8, 0.5, Annual 1 + + + + + + of: + de: + + + + + Issue number: + Número da edição: + + + + Volume: + Volume: + + + + Arc number: + Número do arco: + + + + Story arc: + Arco narrativo: + + + + alt. number: + n.º alt.: + + + + Alternate series: + Série alternativa: + + + + Series Group: + Grupo de séries: + + + + Genre: + Género: + + + + Size: + Tamanho: + + + + Writer(s): + Argumentista(s): + + + + Penciller(s): + Desenhador(es): + + + + Inker(s): + Arte-finalista(s): + + + + Colorist(s): + Colorista(s): + + + + Letterer(s): + Letrista(s): + + + + Cover Artist(s): + Artista(s) da capa: + + + + Editor(s): + Editor(es): + + + + Imprint: + Chancela editorial: + + + + Day: + Dia: + + + + Month: + Mês: + + + + Year: + Ano: + + + + Publisher: + Editora: + + + + Format: + Formato: + + + + Color/BW: + Cor/PB: + + + + Age rating: + Classificação etária: + + + + Type: + Tipo: + + + + Language (ISO): + Idioma (ISO): + + + + Synopsis: + Sinopse: + + + + Characters: + Personagens: + + + + Teams: + Equipas: + + + + Locations: + Locais: + + + + Main character or team: + Personagem principal ou equipa: + + + + Review: + Crítica: + + + + Notes: + Notas: + + + + Tags: + Etiquetas: + + + + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> + Ligação do Comic Vine: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> ver </a> + + + + Not found + Não encontrado + + + + Comic not found. You should update your library. + Banda desenhada não encontrada. Deve atualizar a sua biblioteca. + + + + Edit selected comics information + Editar informações das bandas desenhadas selecionadas + + + + + Track missing issues in "%1" + Registar números em falta em "%1" + + + + Issues: + Números: + + + + Track + Registar + + + + At most %1 issues can be tracked at once. + É possível registar no máximo %1 números de cada vez. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Verifique os números: os intervalos devem ser crescentes e não pode haver partes vazias entre vírgulas. + + + + No new issues to track. + Nenhum número novo para registar. + + + + %n issue(s) will be tracked: %1. + + %n número será registado: %1. + %n números serão registados: %1. + + + + + That is a lot of issues; check the numbers. + São muitos números; verifique a numeração. + + + + %n issue(s) already in the folder. + + %n número já está na pasta. + %n números já estão na pasta. + + + + + %n repeated issue(s). + + %n número repetido. + %n números repetidos. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Números não registados + + + + The missing issues could not be tracked. + Não foi possível registar os números em falta. + + + + Comic skipped + Banda desenhada ignorada + + + + A comic was removed from the library or now has a different file, so it is skipped. + Uma banda desenhada foi removida da biblioteca ou tem agora um ficheiro diferente, pelo que será ignorada. + + + + + Changes not saved + Alterações não guardadas + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Uma banda desenhada que está a editar foi removida da biblioteca ou tem agora um ficheiro diferente. As suas alterações não foram guardadas. + + + + The changes could not be saved. + Não foi possível guardar as alterações. + + + + Invalid cover + Capa inválida + + + + The image is invalid. + A imagem é inválida. + + + + Edit comic information + Editar informações da banda desenhada + + + + QObject + + + 7z lib not found + Biblioteca 7z não encontrada + + + + unable to load 7z lib from ./utils + não foi possível carregar a biblioteca 7z a partir de ./utils + + + + Select custom cover + Selecionar capa personalizada + + + + Images (%1) + Imagens (%1) + + + + The file could not be read or is not valid JSON. + Não foi possível ler o ficheiro ou este não é um JSON válido. + + + + This theme is for %1, not %2. + Este tema é para %1, não para %2. + + + + Libraries + Bibliotecas + + + + Folders + Pastas + + + + Reading Lists + Listas de leitura + + + + ReadingListManagementCoordinator + + + Add new reading lists + Adicionar novas listas de leitura + + + + + List name: + Nome da lista: + + + + Import reading lists + Importar listas de leitura + + + + Reading lists (*.cbl *.json);;All files (*) + Listas de leitura (*.cbl *.json);;Todos os ficheiros (*) + + + + New smart list + Nova lista inteligente + + + + + The smart list could not be saved. + Não foi possível guardar a lista inteligente. + + + + Edit smart list + Editar lista inteligente + + + + Delete folder + Eliminar pasta + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Eliminar a pasta "%1" e todo o seu conteúdo? As pastas e listas serão eliminadas; os seus ficheiros de banda desenhada serão mantidos. + + + + Delete list/label + Eliminar lista/etiqueta + + + + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? + O item selecionado será eliminado; as suas bandas desenhadas ou pastas NÃO serão eliminadas do disco. Tem a certeza? + + + + Rename list name + Mudar o nome da lista + + + + Rename folder + Mudar o nome da pasta + + + + + Folder name: + Nome da pasta: + + + + + + New folder + Nova pasta + + + + + The folder could not be created. + Não foi possível criar a pasta. + + + + Reading list + Lista de leitura + + + + CBL reading list (*.cbl) + Lista de leitura CBL (*.cbl) + + + + JSON reading list (*.json) + Lista de leitura JSON (*.json) + + + + Web page (*.html) + Página Web (*.html) + + + + + Export reading list + Exportar lista de leitura + + + + Exporting reading list... + A exportar lista de leitura... + + + + The file could not be written. + Não foi possível escrever o ficheiro. + + + + Move to folder + Mover para pasta + + + + "%1" could not be moved. + Não foi possível mover "%1". + + + + ReadingListPage + + + %n comic(s) + + %n banda desenhada + %n bandas desenhadas + + + + + %n missing + + %n em falta + %n em falta + + + + + Missing + Em falta + + + + Exported from YACReader on %1 + Exportado do YACReader em %1 + + + + RenameLibraryDialog + + + Rename current library + Mudar o nome da biblioteca atual + + + + Cancel + Cancelar + + + + Rename + Mudar o nome + + + + New Library Name: + Novo nome da biblioteca: + + + + ScraperResultsPaginator + + + Number of volumes found: %1 + Número de volumes encontrados: %1 + + + + + page %1 of %2 + página %1 de %2 + + + + Number of %1 found: %2 + Número de %1 encontrados: %2 + + + + SearchFieldRegistry + + + Text, quoted text + Texto, texto entre aspas + + + + Integer + Inteiro + + + + Boolean (true / false) + Booleano (true / false) + + + + Integer (number of days) + Inteiro (número de dias) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Enumeração (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Título da banda desenhada + + + + Series name + Nome da série + + + + Issue number + Número da edição + + + + Volume identifier + Identificador do volume + + + + Reading format + Formato de leitura + + + + Comic rating + Classificação da banda desenhada + + + + Textual tags + Etiquetas de texto + + + + Writer credit + Crédito de argumentista + + + + Penciller credit + Crédito de desenhador + + + + Inker credit + Crédito de arte-finalista + + + + Colorist credit + Crédito de colorista + + + + Letterer credit + Crédito de letrista + + + + Cover artist credit + Crédito de artista da capa + + + + Editor credit + Crédito de editor + + + + Story arc name + Nome do arco narrativo + + + + Position within a story arc + Posição dentro de um arco narrativo + + + + Number of issues in a story arc + Número de edições num arco narrativo + + + + Characters appearing in the comic + Personagens que aparecem na banda desenhada + + + + Teams appearing in the comic + Equipas que aparecem na banda desenhada + + + + Locations appearing in the comic + Locais que aparecem na banda desenhada + + + + Primary character or team + Personagem ou equipa principal + + + + Comic synopsis + Sinopse da banda desenhada + + + + Publisher name + Nome da editora + + + + Publishing imprint + Chancela editorial da publicação + + + + Publication format + Formato da publicação + + + + Recommended age rating + Classificação etária recomendada + + + + Comic genre + Género da banda desenhada + + + + ISO language code + Código de idioma ISO + + + + Publication date metadata + Metadados da data de publicação + + + + Series grouping metadata + Metadados de agrupamento de séries + + + + Alternate series name + Nome alternativo da série + + + + Alternate issue number + Número alternativo da edição + + + + Alternate series issue count + Quantidade de edições da série alternativa + + + + Number of issues in the series + Número de edições da série + + + + Whether the comic is marked as read + Se a banda desenhada está marcada como lida + + + + Whether reading has started + Se a leitura foi iniciada + + + + Whether metadata has been edited + Se os metadados foram editados + + + + Whether the comic is in color + Se a banda desenhada é a cores + + + + Number of pages + Número de páginas + + + + Comic file name + Nome do ficheiro da banda desenhada + + + + When the item was added + Quando o item foi adicionado + + + + When the comic was last opened + Quando a banda desenhada foi aberta pela última vez + + + + Comic notes + Notas da banda desenhada + + + + Review text + Texto da crítica + + + + Parent folder name + Nome da pasta de nível superior + + + + Default reading format for the folder + Formato de leitura predefinido da pasta + + + + Whether the folder is complete + Se a pasta está completa + + + + Whether the folder is marked as finished + Se a pasta está marcada como concluída + + + + When the folder was updated + Quando a pasta foi atualizada + + + + SearchSingleComic + + + Please provide some additional information for this comic. + Forneça algumas informações adicionais sobre esta banda desenhada. + + + + Series: + Série: + + + + Use exact match search. Disable if you want to find volumes that match some of the words in the name. + Utilizar pesquisa de correspondência exata. Desative se quiser encontrar volumes que correspondam a algumas das palavras do nome. + + + + SearchSyntaxDialog + + + Common + Comum + + + + Credits + Créditos + + + + Story + História + + + + Publication + Publicação + + + + Reading & files + Leitura e ficheiros + + + + Folders + Pastas + + + + Search syntax + Sintaxe de pesquisa + + + + Search every comic and folder field, or build precise queries. + Pesquise em todos os campos das bandas desenhadas e das pastas ou crie consultas precisas. + + + + Quick guide + Guia rápido + + + + Fields (%1) + Campos (%1) + + + + Examples + Exemplos + + + + Start with a simple search + Comece com uma pesquisa simples + + + + Just start typing. Plain text search across all metadata. + Basta começar a escrever. O texto simples é pesquisado em todos os metadados. + + + + %1 -> search for `%1` in any database field + %1 -> pesquisar `%1` em qualquer campo da base de dados + + + + 1. Search everywhere + 1. Pesquisar em todo o lado + + + + Type any text or quoted text. + Escreva qualquer texto ou texto entre aspas. + + + + 2. Target a field + 2. Indicar um campo + + + + Use a field name followed by : or = + Utilize o nome de um campo seguido de : ou = + + + + 3. Combine conditions + 3. Combinar condições + + + + Use AND, OR, NOT and parentheses. + Utilize AND, OR, NOT e parênteses. + + + + Operators + Operadores + + + + : or = + : ou = + + + + contains the text + contém o texto + + + + matches the complete value + corresponde ao valor completo + + + + greater than / at least + maior que / pelo menos + + + + less than / at most + menor que / no máximo + + + + "quoted text" + "texto entre aspas" + + + + keeps spaces inside one value + mantém os espaços dentro de um único valor + + + + Dates and grouping + Datas e agrupamento + + + + added in the last 7 days + adicionado nos últimos 7 dias + + + + added more than 30 days ago + adicionado há mais de 30 dias + + + + group alternatives + agrupa alternativas + + + + Tips: +Spaces act like AND, and searches are not case-sensitive. +Use quotes to include spaces in a value. + Dicas: +Os espaços funcionam como AND e as pesquisas não distinguem maiúsculas de minúsculas. +Utilize aspas para incluir espaços num valor. + + + + Find a field… + Encontrar um campo… + + + + Text can be entered plainly or in quotes. Integer fields support <, <=, > and >=. For date fields, the integer is a number of days (added>7 means added within the last 7 days). + O texto pode ser introduzido diretamente ou entre aspas. Os campos inteiros suportam <, <=, > e >=. Nos campos de data, o inteiro é um número de dias (added>7 significa adicionado nos últimos 7 dias). + + + + Field + Campo + + + + Description + Descrição + + + + Input + Entrada + + + + Example + Exemplo + + + + Examples show the pattern—replace the values with your own. + Os exemplos mostram o padrão — substitua os valores pelos seus. + + + + Query + Consulta + + + + What it finds + O que encontra + + + + Common filters + Filtros comuns + + + + Unread comics + Bandas desenhadas não lidas + + + + Comics in progress + Bandas desenhadas em curso + + + + Highly rated comics + Bandas desenhadas muito bem avaliadas + + + + Comics added in the last 7 days + Bandas desenhadas adicionadas nos últimos 7 dias + + + + Metadata + Metadados + + + + Search by series + Pesquisar por série + + + + Search by writer + Pesquisar por argumentista + + + + Manga comics + Bandas desenhadas manga + + + + Search textual tags + Pesquisar etiquetas de texto + + + + Advanced combinations + Combinações avançadas + + + + Match either writer + Corresponder a qualquer argumentista + + + + Group alternatives + Agrupar alternativas + + + + Exclude a value + Excluir um valor + + + + Older, highly rated comics + Bandas desenhadas mais antigas e muito bem avaliadas + + + + Copy query + Copiar consulta + + + + Spaces behave like AND. Use quotes for phrases and parentheses to control grouping. + Os espaços funcionam como AND. Utilize aspas para frases e parênteses para controlar o agrupamento. + + + + SearchVolume + + + Please provide some additional information. + Forneça algumas informações adicionais. + + + + Series: + Série: + + + + Use exact match search. Disable if you want to find volumes that match some of the words in the name. + Utilizar pesquisa de correspondência exata. Desative se quiser encontrar volumes que correspondam a algumas das palavras do nome. + + + + SelectComic + + + Please select the right comic info. + Selecione as informações corretas da banda desenhada. + + + + comics + bandas desenhadas + + + + loading cover + a carregar capa + + + + loading description + a carregar descrição + + + + comic description unavailable + descrição da banda desenhada indisponível + + + + SelectVolume + + + Please select the right series for your comic. + Selecione a série correta para a sua banda desenhada. + + + + Filter: + Filtro: + + + + volumes + volumes + + + + Nothing found, clear the filter if any. + Nada encontrado, limpe o filtro, se existir. + + + + loading cover + a carregar capa + + + + loading description + a carregar descrição + + + + volume description unavailable + descrição do volume indisponível + + + + SelectedComicsInfoView + + + Unknown + Desconhecido + + + + %1 (%2 unknown) + %1 (%2 desconhecidas) + + + + %1 series + %1 séries + + + + %1 comics selected + %1 bandas desenhadas selecionadas + + + + Read + Lido + + + + In progress + Em curso + + + + Unread + Não lidos + + + + Total pages + Total de páginas + + + + Total size + Tamanho total + + + + Missing + Em falta + + + + Series + Série + + + + SeriesQuestion + + + You are trying to get information for various comics at once. Are they part of the same series? + Está a tentar obter informações sobre várias bandas desenhadas em simultâneo; fazem parte da mesma série? + + + + yes + sim + + + + no + não + + + + ServerConfigDialog + + + + Server connectivity + Conectividade do servidor + + + + Scan to connect + Digitalize para ligar + + + + Devices on this network can reach your library at the address below. + Os dispositivos nesta rede podem aceder à sua biblioteca no endereço abaixo. + + + + IP address + Endereço IP + + + + Port + Porta + + + + Set port + Definir porta + + + + Web interface + Interface web + + + + Copy link + Copiar ligação + + + + Open web UI + Abrir interface web + + + + Enable the server + Ativar o servidor + + + + YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. + O YACReader está disponível para iOS e Android. Descubra-o para <a href='https://ios.yacreader.com'>iOS</a> ou <a href='https://android.yacreader.com'>Android</a>. + + + + SmartListDialog + + + New smart list + Nova lista inteligente + + + + Edit smart list + Editar lista inteligente + + + + Search filters and syntax + Filtros e sintaxe de pesquisa + + + + Name: + Nome: + + + + Search: + Pesquisa: + + + + SortVolumeComics + + + Please sort the list of comics on the left until it matches the comics' information. + Ordene a lista de bandas desenhadas à esquerda até corresponder às informações das bandas desenhadas. + + + + sort comics to match comic information + ordenar bandas desenhadas para corresponder às informações + + + + issues + edições + + + + remove selected comics + remover bandas desenhadas selecionadas + + + + restore all removed comics + restaurar todas as bandas desenhadas removidas + + + + Add the %n other issue(s) of this volume as missing comics + + Adicionar o outro %n número deste volume como banda desenhada em falta + Adicionar os outros %n números deste volume como bandas desenhadas em falta + + + + + ThemeEditorDialog + + + Theme Editor + Editor de temas + + + + + + + + + + + - + - + + + + i + i + + + + Expand all + Expandir tudo + + + + Collapse all + Recolher tudo + + + + Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. + Mantenha premido para fazer piscar o valor selecionado na interface (magenta / alternado / 0↔10). Ao soltar, o original é restaurado. + + + + Search… + Pesquisar… + + + + Light + Claro + + + + Dark + Escuro + + + + ID: + ID: + + + + Display name: + Nome de apresentação: + + + + Variant: + Variante: + + + + Theme info + Informações do tema + + + + Parameter + Parâmetro + + + + Value + Valor + + + + Save and apply + Guardar e aplicar + + + + Export to file... + Exportar para ficheiro... + + + + Load from file... + Carregar a partir de ficheiro... + + + + Close + Fechar + + + + Double-click to edit color + Faça duplo clique para editar a cor + + + + + + + + + true + true + + + + + + + false + false + + + + Double-click to toggle + Faça duplo clique para alternar + + + + Double-click to edit value + Faça duplo clique para editar o valor + + + + + + Edit: %1 + Editar: %1 + + + + Save theme + Guardar tema + + + + + JSON files (*.json);;All files (*) + Ficheiros JSON (*.json);;Todos os ficheiros (*) + + + + Save failed + Falha ao guardar + + + + Could not open file for writing: +%1 + Não foi possível abrir o ficheiro para escrita: +%1 + + + + Load theme + Carregar tema + + + + + + Load failed + Falha ao carregar + + + + Could not open file: +%1 + Não foi possível abrir o ficheiro: +%1 + + + + Invalid JSON: +%1 + JSON inválido: +%1 + + + + Expected a JSON object. + Era esperado um objeto JSON. + + + + TitleHeader + + + SEARCH + PESQUISAR + + + + UpdateLibraryDialog + + + Cancel + Cancelar + + + + Updating... + A atualizar... + + + + Update library + Atualizar biblioteca + + + + ViewSelector + + + View + + + + + VolumeComicsModel + + + title + título + + + + VolumesModel + + + year + ano + + + + issues + edições + + + + publisher + editora + + + + YACReader3DFlowConfigWidget + + + Presets: + Predefinições: + + + + Classic look + Aspeto clássico + + + + Stripe look + Aspeto em faixas + + + + Overlapped Stripe look + Aspeto em faixas sobrepostas + + + + Modern look + Aspeto moderno + + + + Roulette look + Aspeto de roleta + + + + Show advanced settings + Mostrar definições avançadas + + + + Custom: + Personalizado: + + + + View angle + Ângulo de visão + + + + Position + Posição + + + + Cover gap + Espaço entre capas + + + + Central gap + Espaço central + + + + Zoom + Zoom + + + + Y offset + Deslocamento Y + + + + Z offset + Deslocamento Z + + + + Cover Angle + Ângulo das capas + + + + Visibility + Visibilidade + + + + Light + Luz + + + + Max angle + Ângulo máximo + + + + Low Performance + Baixo desempenho + + + + High Performance + Alto desempenho + + + + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Usar VSync (melhora a qualidade da imagem em modo de ecrã inteiro, mas reduz o desempenho) + + + + Performance: + Desempenho: + + + + YACReader::TrayIconController + + + &Restore + &Restaurar + + + + Systray + Área de notificação + + + + YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. + O YACReaderLibrary continuará em execução na área de notificação do sistema. Para terminar o programa, escolha <b>Sair</b> no menu de contexto do ícone da área de notificação. + + + + YACReader::WhatsNewDialog + + + Release notes are not available. + As notas de versão não estão disponíveis. + + + + Previous versions + Versões anteriores + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + A base de dados da biblioteca está em falta. Restaure uma cópia de segurança ou recrie a biblioteca antes de voltar a analisar as informações XML. + + + + Unable to load the archive backend: %1 + Não foi possível carregar o motor de arquivos comprimidos: %1 + + + + Unable to start an XML rescan transaction. + Não foi possível iniciar uma transação de nova análise XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + A pasta selecionada já não está nesta biblioteca. Recarregue a biblioteca antes de voltar a analisar. + + + + Unable to read comics for the XML rescan. + Não foi possível ler as bandas desenhadas para a nova análise XML. + + + + Unable to commit XML metadata changes. + Não foi possível confirmar as alterações nos metadados XML. + + + + Unable to update XML metadata for %1. + Não foi possível atualizar os metadados XML de %1. + + + + YACReaderFieldEdit + + + + Click to overwrite + Clique para substituir + + + + Restore to default + Restaurar predefinição + + + + YACReaderFieldPlainTextEdit + + + + + + + + Click to overwrite + Clique para substituir + + + + Restore to default + Restaurar predefinição + + + + YACReaderOptionsDialog + + + Save + Guardar + + + + Cancel + Cancelar + + + + Keyboard shortcuts + Atalhos de teclado + + + + Customize the keyboard shortcuts used by the application. + Personalize os atalhos de teclado utilizados pela aplicação. + + + + Edit shortcuts + Editar atalhos + + + + Shortcuts + Atalhos + + + + YACReaderSearchLineEdit + + + Search filters + Filtros de pesquisa + + + + type to search + escreva para pesquisar + + + diff --git a/YACReaderLibrary/yacreaderlibrary_ru.ts b/YACReaderLibrary/yacreaderlibrary_ru.ts index 7ca78ac23..f59166874 100644 --- a/YACReaderLibrary/yacreaderlibrary_ru.ts +++ b/YACReaderLibrary/yacreaderlibrary_ru.ts @@ -6,20 +6,80 @@ None - Никто + Нет AddLabelDialog + + + red + Красный + + + + orange + Оранжевый + + + + yellow + Жёлтый + + + + green + Зелёный + + + + cyan + Голубой + + + + blue + Синий + + + + violet + Фиолетовый + + + + purple + Пурпурный + + + + pink + Розовый + + + + white + Белый + + + + light + Светлый + + + + dark + Тёмный + Choose a color: - Выбрать цвет: + Выберите цвет: accept - добавить + принять @@ -42,7 +102,7 @@ Add an existing library - Добавить в существующую библиотеку + Добавить существующую библиотеку @@ -51,13 +111,13 @@ - Comics folder : - Папка комиксов : + Comics folder: + Папка комиксов: - Library name : - Имя библиотеки : + Library name: + Имя библиотеки: @@ -74,13 +134,13 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - Для подключения к Comic Vine вам потребуется ваш личный API ключ. Приобретите его бесплатно вот <a href="http://www.comicvine.com/api/">здесь</a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + Для подключения к Comic Vine вам потребуется ваш личный ключ API. Получите его бесплатно <a href="http://www.comicvine.com/api/">здесь</a> Paste here your Comic Vine API key - Вставьте сюда ваш Comic Vine API ключ + Вставьте сюда ваш ключ API Comic Vine @@ -98,22 +158,22 @@ Light - Осветить + Светлая Dark - Темный + Темная Custom - Обычай + Пользовательская Remove - Удалять + Удалить @@ -123,17 +183,17 @@ Light: - Свет: + Светлая: Dark: - Темный: + Темная: Custom: - Пользовательский: + Пользовательская: @@ -200,103 +260,630 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Скрыть Comic Flow + + Find comic › + Найти комикс › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Скопировать комиксы... + + Series + Серия - Moving comics... - Переместить комиксы... + + Number + Номер + + + + Volume + Том + + + + Year + Год + + + + Format + Формат + + + + + ComicVine ID + ID Comic Vine + + + + series and number + серия и номер + + + + series without the volume, and number + серия без тома и номер + + + + letters and digits of the series, and number + буквы и цифры серии и номер + + + + series with its year, and number + серия с годом и номер + + + + volume %1 + том %1 + + + + Comic in your library + Комикс в вашей библиотеке + + + + Missing in your library + Отсутствует в вашей библиотеке + + + + Changed by you. + Изменено вами. + + + + The entry has this comic. + Запись связана с этим комиксом. + + + + Matched by %1. + Сопоставлено по %1. + + + + Your library already has this comic as missing. Matched by %1. + Этот комикс уже отмечен в библиотеке как отсутствующий. Сопоставлено по %1. + + + + Possible comic %1 + Возможный комикс %1 + + + + + Changed by you: it is imported as missing. + Изменено вами: будет импортировано как отсутствующий комикс. + + + + The comics have the same ComicVine ID. + У комиксов одинаковый ID Comic Vine. + + + + The comic has the same series and number, but its volume or year is different. + У комикса та же серия и номер, но другой том или год. + + + + The comics have the same series and number. + У комиксов одинаковая серия и номер. + + + + Values that are different from the file are in amber. + Значения, отличающиеся от файла, выделены янтарным цветом. + + + + Closest series in your library + Наиболее похожие серии в библиотеке + + + + Library + Библиотека + + + + Has %1 + Есть %1 + + + + No comic found. Tried: %1. + Комикс не найден. Проверено: %1. + + + + In the file + В файле + + + + Entry %1 of %2 + Запись %1 из %2 + + + + Use this comic + Использовать этот комикс + + + + Use this missing comic + Использовать этот отсутствующий комикс + + + + CblFindComicPage + + + ‹ All entries + ‹ Все записи + + + + Previous problem + Предыдущая проблема + + + + Next problem + Следующая проблема + + + + Folders + Папки + + + + Search the whole library + Искать по всей библиотеке + + + + File + Файл + + + + Series + Серия + + + + No. + № + + + + Volume + Том + + + + Year + Год + + + + Go to the next problem after I choose + Перейти к следующей проблеме после выбора + + + + Use this comic + Использовать этот комикс + + + + Cancel + Отмена + + + + Search in this folder + Искать в этой папке + + + + Entry %1 of %2 + Запись %1 из %2 + + + + Missing: no comic found. Browse or search the library. + Отсутствует: комикс не найден. Просмотрите библиотеку или выполните поиск. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Неоднозначно: %n возможный комикс. Просмотрите библиотеку или выполните поиск. + Неоднозначно: %n возможных комикса. Просмотрите библиотеку или выполните поиск. + Неоднозначно: %n возможных комиксов. Просмотрите библиотеку или выполните поиск. + + + + + Browse or search the library to choose another comic. + Просмотрите библиотеку или выполните поиск, чтобы выбрать другой комикс. + + + + Problem %1 of %2 + Проблема %1 из %2 + + + + %n problem(s) + + %n проблема + %n проблемы + %n проблем + + + + + + + Library + Библиотека + + + + Search results + Результаты поиска + + + + CblImportDialog + + + Match to library comics — %1 + Сопоставление с комиксами библиотеки — %1 + + + + Apply + Применить + + + + Close + Закрыть + + + + Skip + Пропустить + + + + Import + Импортировать + + + + &Name: + &Название: + + + + A reading list with this name already exists. Import creates another one. + Список чтения с таким именем уже существует. При импорте будет создан ещё один. + + + + Filter entries + Фильтр записей + + + + Matching the books of %1 with your library... + Сопоставление книг из %1 с библиотекой... + + + + Matching the entries of %1 with your library... + Сопоставление записей из %1 с библиотекой... + + + + + Match to library comics + Сопоставить с комиксами библиотеки + + + + This also gives comics to entries in %n other list(s): %1 + + Комиксы также будут связаны с записями в %n другом списке: %1 + Комиксы также будут связаны с записями в %n других списках: %1 + Комиксы также будут связаны с записями в %n других списках: %1 + + + + + The changes could not be applied. Nothing was changed. + Не удалось применить изменения. Ничего не изменено. + + + + The file cannot be opened. + Не удаётся открыть файл. + + + + The file is damaged (line %1, column %2). + Файл повреждён (строка %1, столбец %2). + + + + The file is not a reading list. + Файл не является списком чтения. + + + + The library cannot be read. + Не удаётся прочитать библиотеку. + + + + Import reading lists + Импорт списков чтения + + + + The reading list could not be imported. Nothing was written. + Не удалось импортировать список чтения. Ничего не записано. + + + + Import reading lists — %1 (%2 of %3) + Импорт списков чтения — %1 (%2 из %3) + + + + Import reading lists — %1 + Импорт списков чтения — %1 + + + + All (%1) + Все (%1) + + + + Matched (%1) + Сопоставленные (%1) + + + + Ambiguous (%1) + Неоднозначные (%1) + + + + Missing (%1) + Отсутствующие (%1) + + + + Changed by you (%1) + Изменённые вами (%1) + + + + %1 of %n have a comic + + С комиксом: %1 из %n + С комиксом: %1 из %n + С комиксом: %1 из %n + + + + + Find comic... + Найти комикс... + + + + Use the original match + Использовать исходное соответствие + + + + Make missing + Отметить как отсутствующий + + + + Import as missing + Импортировать как отсутствующий + + + + CblImportModel + + + Matched + Сопоставлено + + + + Ambiguous + Неоднозначно + + + + Missing + Отсутствует + + + + Changed + Изменено + + + + # + # + + + + In the file + В файле + + + + Comic in your library + Комикс в вашей библиотеке + + + + Status + Состояние + + + + Already missing in your library + Уже отмечен в библиотеке как отсутствующий + + + + %n possible comic(s) + + %n возможный комикс + %n возможных комикса + %n возможных комиксов + + + + + Not in library + Нет в библиотеке + + + + CblMatchComicDialog + + + Match to library comic + Сопоставить с комиксом библиотеки + + + + Loading the library... + Загрузка библиотеки... + + + + The library cannot be read. + Не удаётся прочитать библиотеку. + + + + ClassicComicsView + + + Hide comic flow + Скрыть Comic Flow ComicInfoView - + Characters Персонажи - + Main character or team Главный герой или команда - + Teams Команды - + Locations - Местоположение + Локации - + Authors Авторы - + writer сценарист - + penciller художник - + inker - инкер + контуровщик - + colorist колорист - + letterer леттерер - + cover artist художник обложки - + editor редактор - + imprint импринт - + Publisher Издатель - + color цвет - + b/w ч/б @@ -304,28 +891,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader не найден - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader не найден. YACReader должен быть установлен в ту же папку, что и YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. YACReader не найден. Возможно, возникла проблема с установкой YACReader. - + Error Ошибка - + Error opening comic with third party reader. Ошибка при открытии комикса с помощью сторонней программы чтения. @@ -333,67 +920,67 @@ ComicModel - + no нет - + yes да - + Read Прочитано - + Series - Ряд + Серия - + Volume - Объем + Том - + Story Arc Сюжетная арка - + Size Размер - + Pages - Всего страниц + Страницы - + Title - Заголовок + Название - + Current Page Текущая страница - + File Name Имя файла - + Publication Date Дата публикации - + Rating Рейтинг @@ -401,68 +988,115 @@ ComicVineDialog - + back назад - + next дальше - + skip пропустить - + close закрыть - - - Retrieving tags for : %1 - Получение тегов для : %1 + + + Retrieving tags for: %1 + Получение тегов для: %1 + + + + Creating missing comic %1 of %2 + Создание отсутствующего комикса %1 из %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine не ответил для %n комикса. + Comic Vine не ответил для %n комиксов. + Comic Vine не ответил для %n комиксов. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n отсутствующий комикс получил только основные данные тома. + %n отсутствующих комикса получили только основные данные тома. + %n отсутствующих комиксов получили только основные данные тома. + + + + + %n missing comic(s) have no cover. + + У %n отсутствующего комикса нет обложки. + У %n отсутствующих комиксов нет обложки. + У %n отсутствующих комиксов нет обложки. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine разрешает около 200 запросов в час; повторите попытку позже для необновлённых комиксов. + + + + Try again later for the comics that were not updated. + Повторите попытку позже для необновлённых комиксов. + + + + Comic Vine + Comic Vine - + Looking for comic... Поиск комикса... - + search искать - - - + + + Looking for volume... - Поиск информации... + Поиск тома... - - + + comic %1 of %2 - %3 - комикс %1 of %2 - %3 + комикс %1 из %2 - %3 - + %1 comics selected - %1 было выбрано + Выбрано комиксов: %1 - - Error connecting to ComicVine - Ошибка поключения к ComicVine + + Error connecting to Comic Vine + Ошибка подключения к Comic Vine - + Retrieving volume info... - Получение информации... + Получение информации о томе... @@ -492,23 +1126,23 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. Создание библиотеки может занять несколько минут. Вы можете остановить процесс и обновить библиотеку позже для завершения задачи. The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - Выбранный путь отсутствует, либо неверен. Убедитесь , что у вас есть доступ к этой папке + Выбранный путь не существует или недопустим. Убедитесь, что у вас есть доступ на запись в эту папку - Comics folder : - Папка комиксов : + Comics folder: + Папка комиксов: - Library Name : - Имя библиотеки: + Library Name: + Имя библиотеки: @@ -519,11 +1153,19 @@ DBHelper - + The folder entry could not be found in the library database. Запись о папке не найдена в базе данных библиотеки. + + DelayedProgressDialog + + + Cancel + Отмена + + EditShortcutsDialog @@ -543,13 +1185,13 @@ - The shortcut "%1" is already assigned to other function - Сочетание клавиш "%1" уже назначено для другой функции + The shortcut "%1" is already assigned to another function + Сочетание клавиш «%1» уже назначено для другой функции - To change a shortcut, double click in the key combination and type the new keys. - Чтобы изменить горячую клавишу щелкните дважды по выбранной комбинации клавиш и введите новые сочетания клавиш. + To change a shortcut, double-click the key combination and type the new keys. + Чтобы изменить горячую клавишу, щелкните дважды по выбранной комбинации клавиш и введите новые сочетания клавиш. @@ -581,6 +1223,14 @@ Этот ярлык пока ничего не содержит + + EmptyListFolderWidget + + + This folder is empty + Эта папка пуста + + EmptyReadingListWidget @@ -599,7 +1249,7 @@ You are not reading anything yet, come on!! - Вы пока ничего не читаете. Может самое время почитать? + Вы пока ничего не читаете. Может, самое время почитать? @@ -611,13 +1261,13 @@ ExportComicsInfoDialog - Output file : - Выходной файл (*.ydb) : + Output file: + Выходной файл (*.ydb): Destination database name - Имя этой базы данных + Имя базы данных назначения @@ -632,7 +1282,7 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Выбранный путь для импортируемого файла отсутствует, либо неверен. Убедитесь , что у вас есть доступ к этой папке + Выбранный путь для выходного файла не существует или недопустим. Убедитесь, что у вас есть доступ на запись в эту папку @@ -642,7 +1292,7 @@ Problem found while writing - Обнаружена Ошибка записи + Проблема при записи @@ -650,7 +1300,7 @@ Cancel - Отменить + Отмена @@ -660,17 +1310,17 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Выбранный путь для импортируемого файла отсутствует, либо неверен. Убедитесь , что у вас есть доступ к этой папке + Выбранный путь для выходного файла не существует или недопустим. Убедитесь, что у вас есть доступ на запись в эту папку - Output folder : - Папка вывода : + Output folder: + Папка вывода: Problem found while writing - Проблема при написании + Проблема при записи @@ -680,40 +1330,33 @@ Destination directory - Назначенная директория + Папка назначения FileComic - - + + Format not supported Формат не поддерживается - + 7z not found 7z не найден - + Unknown error opening the file Неизвестная ошибка при открытии файла - + CRC error on page (%1): some of the pages will not be displayed correctly Ошибка контрольной суммы CRC на странице (%1): некоторые страницы будут отображаться неправильно - - FolderContentView - - Continue Reading... - Продолжить чтение... - - FolderInfoView @@ -788,53 +1431,37 @@ GridComicsView - + Show info Показать информацию - Library - Библиотека - - - Folder - Папка - - - Favorites - Избранное - - - Recently added - Недавно добавленные - - - + Manga Манга - + Western manga Западная манга - + Web comic Веб-комикс - + Yonkoma Ёнкома - + Comic Комикс - + Unknown Неизвестно @@ -844,7 +1471,7 @@ Help - Настройки + Справка @@ -876,8 +1503,8 @@ - Info database location : - Путь к файлу (*.ydb) : + Info database location: + Путь к файлу (*.ydb): @@ -894,8 +1521,8 @@ ImportLibraryDialog - Destination folder : - Папка назначения : + Destination folder: + Папка назначения: @@ -909,18 +1536,18 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) Сжатая библиотека обложек (*.clc) - Package location : - Местоположение комплекта : + Package location: + Местоположение комплекта: - Library Name : - Имя библиотеки : + Library Name: + Имя библиотеки: @@ -942,13 +1569,13 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderLibrary сейчас создает библиотеку.</p><p> Создание библиотеки может занять несколько минут. Вы можете остановить процесс и обновить библиотеку позже для завершения задачи.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary сейчас создает новую библиотеку.</p><p>Создание библиотеки может занять несколько минут. Вы можете остановить процесс и обновить библиотеку позже для завершения задачи.</p> Some of the comics being added... - Поиск новых комиксов... + Добавляются некоторые комиксы... @@ -957,8 +1584,8 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>Текущая библиотека обновляется. Для более быстрого обновления в дальнейшем старайтесь почаще обновлять вашу библиотеку после добавления новых комиксов.</p><p>Вы можете остановить этот процесс и продолжить обновление этой библиотеки позже.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>Текущая библиотека обновляется. Для более быстрого обновления старайтесь почаще обновлять ваши библиотеки.</p><p>Вы можете остановить этот процесс и продолжить обновление этой библиотеки позже.</p> @@ -977,7 +1604,7 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> <p>Текущая библиотека сканируется на предмет устаревших метаданных XML.</p><p>Это необходимо только один раз и только в том случае, если библиотека была создана с помощью YACReaderLibrary 9.8.2 или более ранней версии.</p> @@ -986,6 +1613,36 @@ <p>Текущая библиотека проверяется на отсутствующие обложки и неполные сведения о комиксах.</p><p>Это может занять несколько минут. Процесс можно остановить и запустить снова позже.</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Не удалось создать папку данных библиотеки: %1. Проверьте права доступа и свободное место на диске. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Отсутствует папка данных библиотеки: %1. Восстановите её или создайте библиотеку заново перед обновлением. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Отсутствует база данных библиотеки: %1. Восстановите резервную копию или создайте библиотеку заново перед обновлением. + + + + + Unable to start a library database transaction: %1 + Не удалось начать транзакцию базы данных библиотеки: %1 + + + + + Unable to commit the library database: %1 + Не удалось зафиксировать транзакцию базы данных библиотеки: %1 + + LibraryInfoView @@ -1009,33 +1666,125 @@ Количество прочитанных комиксов + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Библиотека занята. Дождитесь окончания обслуживания. Если оно зависло, закройте приложение, указанное в разделе «Подробности», и повторите попытку. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Предыдущая операция обслуживания могла не освободить библиотеку. Перед восстановлением доступа убедитесь, что её не использует ни одна операция YACReaderLibrary или YACReaderLibraryServer, в том числе на другом компьютере. + + + + Retry + Повторить + + + + Recover and retry + Восстановить доступ и повторить + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Владелец блокировки библиотеки изменился. Повторите операцию и проверьте нового владельца перед восстановлением доступа. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Не удалось удалить блокировку обслуживания: %1. Убедитесь, что файл не доступен только для чтения и у вас есть право записи в папку данных библиотеки, затем повторите попытку. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Путь файла блокировки обслуживания занят папкой: %1. Переименуйте или переместите эту папку перед повторной попыткой. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Не удалось создать файл блокировки библиотеки: %1. Проверьте права записи в папку данных библиотеки. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Отсутствует папка данных библиотеки: %1. Восстановите папку или создайте библиотеку заново перед обновлением. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + У библиотеки есть блокировка обслуживания, владельца которой не удалось проверить (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Эту библиотеку использует другая операция обслуживания (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Не удалось создать файл блокировки библиотеки: %1. Проверьте свободное место на диске и доступ к папке данных библиотеки. + + LibraryManagementCoordinator - + Error opening the library Ошибка открытия библиотеки - + Error creating the library Ошибка создания библиотеки - + Error updating the library Ошибка обновления библиотеки + + + + XML information rescan + Повторное сканирование XML + + + + Restore recovery + Восстановление после сбоя восстановления + + + + Library upgrade + Обновление библиотеки + + + + Library maintenance + Обслуживание библиотеки + + + + Recover library access + Восстановить доступ к библиотеке + LibraryWindow - + The selected folder doesn't contain any library. Выбранная папка не содержит ни одной библиотеки. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? Эта библиотека была создана с предыдущей версией YACReaderLibrary. Она должна быть обновлена. Обновить сейчас? @@ -1047,22 +1796,18 @@ The selected folder and all its contents will be deleted from your disk. Are you sure? - Выбранная папка и все ее содержимое будет удалено с вашего жёсткого диска. Вы уверены? + Выбранная папка и всё её содержимое будут удалены с вашего жёсткого диска. Вы уверены? - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - Возникла проблема при удалении выбранных папок. Пожалуйста, проверьте права на запись и убедитесь что другие приложения не используют эти папки или файлы. - - - Remove and delete metadata - Удаление метаданных + Возникла проблема при удалении выбранных папок. Пожалуйста, проверьте права на запись и убедитесь, что другие приложения не используют эти папки или файлы в них. - + Old library - Библиотека из старой версии YACreader + Старая библиотека @@ -1070,34 +1815,34 @@ Ошибка доступа к пути папки - + Comics will only be deleted from the current label/list. Are you sure? Комиксы будут удалены только из выбранного списка/ярлыка. Вы уверены? - + This library was created with a newer version of YACReaderLibrary. Download the new version now? Эта библиотека была создана новой версией YACReaderLibrary. Скачать новую версию сейчас? - + Moving comics... - Переместить комиксы... + Перемещение комиксов... - + Copying comics... - Скопировать комиксы... + Копирование комиксов... - + Library '%1' is no longer available. Do you want to remove it? - Библиотека '%1' больше не доступна. Вы хотите удалить ее? + Библиотека «%1» больше не доступна. Вы хотите удалить её? - - Do you want remove - Вы хотите удалить библиотеку + + Do you want to remove the library '%1'? + Вы хотите удалить библиотеку «%1»? @@ -1105,126 +1850,159 @@ Ошибка в пути - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - Библиотека '%1' была создана старой версией YACReaderLibrary. Она должна быть вновь создана. Вы хотите создать библиотеку сейчас? + Библиотека «%1» была создана старой версией YACReaderLibrary. Она должна быть создана заново. Вы хотите создать библиотеку сейчас? - + Save covers Сохранить обложки - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. Вы добавляете слишком много библиотек. Вероятно, вам нужна только одна библиотека в папке комиксов верхнего уровня, вы можете просматривать любые подпапки, используя раздел папок на левой боковой панели. -YACReaderLibrary не помешает вам создать больше библиотек, но вы должны иметь не большое количество библиотек. +YACReaderLibrary не помешает вам создать больше библиотек, но вам следует ограничить их количество. - + Library info Информация о библиотеке - + Assign comics numbers - Порядковый номер + Назначить номера комиксам - Please, select a folder first + Please select a folder first Пожалуйста, сначала выберите папку - + Library not available - Библиотека не доступна + Библиотека недоступна - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. Возникла проблема при удалении выбранных комиксов. Пожалуйста, проверьте права на запись для выбранных файлов или содержащую их папку. - + YACReader Library Библиотека YACReader - + You are adding too many libraries. Вы добавляете слишком много библиотек. - + Update needed Необходимо обновление - + Library name already exists - Имя папки уже используется + Имя библиотеки уже существует - + There is another library with the name '%1'. - Уже существует другая папка с именем '%1'. + Уже существует другая библиотека с именем «%1». + Delete folder Удалить папку - - Assign numbers starting in: - Назначить порядковый номер начиная с: + + Assign numbers starting at: + Назначить номера, начиная с: - + Download new version Загрузить новую версию - + Remove and delete metadata and backups Удалить библиотеку, метаданные и резервные копии - + Invalid image Неверное изображение - + The selected file is not a valid image. Выбранный файл не является допустимым изображением. - + + Error saving cover - Не удалось сохранить обложку. + Не удалось сохранить обложку - + + There was an error saving the cover image. Не удалось сохранить изображение обложки. - + + + Match to library comic + Сопоставить с комиксом библиотеки + + + + This also gives the comic to entries in %n other list(s): %1 + + Комикс также будет связан с записями в %n другом списке: %1 + Комикс также будет связан с записями в %n других списках: %1 + Комикс также будет связан с записями в %n других списках: %1 + + + + + The comic could not be matched. Nothing was changed. + Не удалось сопоставить комикс. Ничего не изменено. + + + + The selected missing comics will be removed from this folder. Are you sure? + Выбранные отсутствующие комиксы будут удалены из этой папки. Вы уверены? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + Выбранные комиксы будут удалены с диска, а выбранные отсутствующие комиксы — из этой папки. Вы уверены? + + + Delete comics Удалить комиксы @@ -1236,98 +2014,137 @@ YACReaderLibrary не помешает вам создать больше биб Ни одна папка не была выбрана - + All the selected comics will be deleted from your disk. Are you sure? Все выбранные комиксы будут удалены с вашего жёсткого диска. Вы уверены? - + + Remove the comics from Reading? Their reading progress will be reset. + Убрать комиксы из раздела «Читаю»? Прогресс чтения будет сброшен. + + + + The comics will be removed from Favorites. Are you sure? + Комиксы будут убраны из избранного. Вы уверены? + + + Remove comics Убрать комиксы - + Library not found Библиотека не найдена - + Unable to delete Не удалось удалить - + + Edit smart list... + Изменить умный список... + + + + Delete smart list + Удалить умный список + + + + + + New folder... + Новая папка... + + + + + + Move to folder... + Переместить в папку... + + + + Rename folder... + Переименовать папку... + + + + Match to library comics... + Сопоставить с комиксами библиотеки... + + + + Track missing issues... + Отслеживать недостающие выпуски... + + + + Export reading list... + Экспортировать список чтения... + + + Rename or organize files Переименовать или упорядочить файлы - + Set the type of the selected comics Задать тип выбранных комиксов - + + Save as smart list… + Сохранить как умный список… + + + Search filters Фильтры поиска - + Unread Непрочитанные - + In progress В процессе - + Highly rated С высокой оценкой - + Recently added Недавно добавленные - + Search syntax… Синтаксис поиска… - - A repair of this library is already running (%1). Wait for it to finish. - Восстановление этой библиотеки уже выполняется (%1). Дождитесь его завершения. - - - - The library is locked by a repair that did not finish. - Библиотека заблокирована незавершённым восстановлением. - - - - The library is locked by a repair started by %1. - Библиотека заблокирована восстановлением, запущенным %1. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Если вы уверены, что никакое другое восстановление не выполняется, блокировку можно снять. Снять блокировку и продолжить? - - - + Package operation failed Не удалось выполнить операцию с пакетом - + The covers package operation could not be completed. Не удалось завершить операцию с пакетом обложек. - + Restore recovery failed Не удалось восстановиться после прерванного восстановления @@ -1378,48 +2195,42 @@ Folder: %1 Не удалось обновить базу данных библиотеки, и переименование папки на диске тоже не удалось отменить. Теперь библиотеку нужно обновить вручную. - - + + YACReader library database (*.ydb) База данных библиотеки YACReader (*.ydb) - + The library database backup was created at: %1 Резервная копия базы данных библиотеки создана здесь: %1 - + Unable to create the library database backup: %1 Не удалось создать резервную копию базы данных библиотеки: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? Перед восстановлением закройте YACReaderLibraryServer и все другие приложения YACReader, использующие эту библиотеку. Продолжить? - + Restoring library database... Восстановление базы данных библиотеки... - + The current library database is invalid. Restore the selected backup anyway? Текущая база данных библиотеки повреждена. Всё равно восстановить выбранную резервную копию? - - - The library maintenance lock may be stale. Remove it and retry? - Файл блокировки обслуживания библиотеки может быть устаревшим. Удалить его и повторить попытку? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1428,71 +2239,77 @@ Restart YACReaderLibrary before attempting recovery again. Перезапустите YACReaderLibrary перед следующей попыткой восстановления. - + The library database was restored successfully. Update the library now? База данных библиотеки успешно восстановлена. Обновить библиотеку сейчас? - + Library database damaged База данных библиотеки повреждена - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. База данных библиотеки «%1» повреждена, поэтому обычные обновления, обслуживание и резервное копирование недоступны. YACReader может попытаться восстановить базу данных. Некоторые повреждённые данные могут быть утрачены. Существующие резервные копии не будут изменены. - + Attempt repair Попытаться восстановить - + Restore a backup... Восстановить резервную копию... - + Repairing library database... - Восстановление базы данных библиотеки... + Исправление базы данных библиотеки... - - + + + Library database repair Восстановление базы данных библиотеки - - Another maintenance operation is currently using this library. Try again after it finishes. - Сейчас эту библиотеку использует другая операция обслуживания. Повторите попытку после её завершения. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + Проверить базу данных библиотеки '%1' и при необходимости попытаться исправить её? Существующие резервные копии не будут изменены. - + + Check and repair + Проверить и исправить + + + The library database is already valid. База данных библиотеки уже исправна. - + Library database repaired База данных библиотеки восстановлена - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 База данных библиотеки восстановлена путём перестроения индексов. Повреждённый оригинал сохранён здесь: %1 - + Library database rebuilt База данных библиотеки перестроена - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1503,7 +2320,7 @@ Update the library now? Обновить библиотеку сейчас? - + The damaged original was preserved at: @@ -1514,12 +2331,12 @@ The damaged original was preserved at: %1 - + Library database repair failed Не удалось восстановить базу данных библиотеки - + The library database could not be repaired: %1%2 @@ -1530,27 +2347,22 @@ You can restore a backup from the Library menu or recreate the library. Можно восстановить резервную копию из меню «Библиотека» или создать библиотеку заново. - - library? - ? - - - + Are you sure? Вы уверены? - + Upgrade failed Обновление не удалось - + There were errors during library upgrade in: При обновлении библиотеки возникли ошибки: - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1635,433 +2447,504 @@ Missing files: %3 + Repair library database... + Исправить базу данных библиотеки... + + + + Check the current library database and attempt to repair any damage + Проверить текущую базу данных библиотеки и попытаться исправить повреждения + + + + Recover library access... + Восстановить доступ к библиотеке... + + + + Recover access after an interrupted library maintenance operation + Восстановить доступ после прерванного обслуживания библиотеки + + + Repair covers and comic info Восстановить обложки и сведения о комиксах - + Retry comics with missing covers or incomplete information Повторно обработать комиксы с отсутствующими обложками или неполными сведениями - + Rename library Переименовать библиотеку - + Rename current library Переименовать эту библиотеку - + Remove library Удалить библиотеку - + Remove current library from your collection Удалить эту библиотеку из своей коллекции - + Rescan library for XML info Повторное сканирование библиотеки для получения информации XML - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. Пытается найти информацию XML, встроенную в файлы комиксов. Это необходимо делать только в том случае, если библиотека была создана с помощью версии 9.8.2 или более ранней, или если вы используете стороннее программное обеспечение для встраивания информации XML в файлы. - + Open library folder... Открыть папку библиотеки... - + Open the root folder of the current library Открыть корневую папку текущей библиотеки - + Show library info Показать информацию о библиотеке - + Show information about the current library Показать информацию о текущей библиотеке - + Open current comic Открыть выбранный комикс - - Open current comic on YACReader + + Open current comic in YACReader Открыть комикс в YACReader - + Save selected covers to... Сохранить выбранные обложки в... - + Save covers of the selected comics as JPG files Сохранить обложки выбранных комиксов как JPG файлы - - + + Set as read Отметить как прочитано - + Set comic as read Отметить комикс как прочитано - - + + Set as unread Отметить как не прочитано - + Set comic as unread Отметить комикс как не прочитано - - + + manga манга - + Set issue as manga Установить выпуск как мангу - - + + comic комикс - + Set issue as normal - Установите проблему как обычно + Установить выпуск как обычный комикс - + western manga - вестерн манга + западная манга - + Set issue as western manga Установить выпуск как западную мангу - - + + web comic веб-комикс - + Set issue as web comic Установить выпуск как веб-комикс - - + + yonkoma - йонкома + ёнкома - + Set issue as yonkoma - Установить проблему как йонкома + Установить выпуск как ёнкома - + Show/Hide marks - Показать/Спрятать пометки + Показать/скрыть пометки - + Show or hide read marks - Показать или спрятать отметку прочтено + Показать или скрыть отметки о прочтении - + Show/Hide recent indicator - Показать/скрыть индикатор последних событий + Показать/скрыть индикатор недавно добавленных - + Show or hide recent indicator - Показать или скрыть недавний индикатор + Показать или скрыть индикатор недавно добавленных - - + + Fullscreen mode on/off Полноэкранный режим включить/выключить - + Help, About YACReader - О программе + Справка, о программе YACReader - + Add new folder Добавить новую папку - + Add new folder to the current library Добавить новую папку в текущую библиотеку - + Rename folder Переименовать папку - + Rename the current folder on disk and in the library Переименовать текущую папку на диске и в библиотеке - + Delete folder Удалить папку - + Delete current folder from disk Удалить выбранную папку с жёсткого диска - + Select root node - Домашняя папка + Выбрать корневой узел - + Expand all nodes Раскрыть все папки - + Collapse all nodes Свернуть все папки - + Show options dialog Настройки - + Show comics server options dialog Настройки сервера YACReader - - + + Change between comics views - Изменение внешнего вида потока комиксов + Переключение между видами комиксов + + + + Grid view + Вид сеткой + + + + Flow view + + + + + Info view + - + Open folder... Открыть папку... - - + + Rename files... Organize files Переименовать файлы... - - + + Organize into folders... Разложить по папкам... - + Set as uncompleted Отметить как не завершено - + Set as completed Отметить как завершено - + Set custom cover - Установить собственную обложку + Установить пользовательскую обложку - + Delete custom cover Удалить пользовательскую обложку - + western manga (left to right) западная манга (слева направо) - + Open containing folder... - Открыть выбранную папку... + Открыть папку, содержащую комикс... - Reset comic rating - Сбросить рейтинг комикса + + New reading list + Новый список чтения - + Select all comics Выбрать все комиксы - + Edit Редактировать информацию - + Assign current order to comics - Назначить порядковый номер + Назначить комиксам номера по текущему порядку - + Update cover Обновить обложки - + Delete selected comics - Удалить выбранное + Удалить выбранные комиксы - + Delete metadata from selected comics Удалить метаданные из выбранных комиксов - + Download tags from Comic Vine Скачать теги из Comic Vine - + Focus search line - Строка поиска фокуса + Перейти к строке поиска - + Focus comics view - Просмотр комиксов в фокусе + Перейти к просмотру комиксов - + Edit shortcuts Редактировать горячие клавиши - + &Quit - &Qкостюм + &Выход - + Update folder Обновить папку - + Update current folder Обновить выбранную папку - + Scan legacy XML metadata Сканировать устаревшие метаданные XML - - Add new reading list - Создать новый список чтения + + Add a new reading list to the current library + Создать новый список чтения в текущей библиотеке + + + + New smart list... + Новый умный список... - - Add a new reading list to the current library - Создать новый список чтения + + Add a saved search to the current library + Добавить сохранённый поиск в текущую библиотеку + + + + New folder... + Новая папка... + + + + Add a folder for reading lists or smart lists + Добавить папку для списков чтения или умных списков + + + + Import reading lists... + Импортировать списки чтения... + + + + Import CBL reading lists into the current library + Импортировать списки чтения CBL в текущую библиотеку - + + New + Создать + + + + Add or import reading lists + Добавить или импортировать списки чтения + + + Remove reading list Удалить список чтения - + Remove current reading list from the library - Удалить выбранный ярлык/список чтения + Удалить текущий список чтения из библиотеки - + Add new label Создать новый ярлык - + Add a new label to this library - Создать новый ярлык + Создать новый ярлык в этой библиотеке - + Rename selected list Переименовать выбранный список - + Rename any selected labels or lists - Переименовать выбранный ярлык/список чтения + Переименовать любые выбранные ярлыки или списки - + Add to... Добавить в... - + Favorites Избранное - + Add selected comics to favorites list Добавить выбранные комиксы в список избранного - - + + Reset rating Сбросить рейтинг @@ -2090,91 +2973,104 @@ Missing files: %3 - 4koma (top to botom) + 4koma (top to bottom) 4кома (сверху вниз) - - - + + + Set type - Тип установки + Задать тип - + Library Библиотека - + Folder Папка - + Comic Комикс - + + Match to library comic... + Сопоставить с комиксом библиотеки... + + + + + + + Track missing issues... + Отслеживать недостающие выпуски... + + + Open folder... Открыть папку... - + Update folder Обновить папку - + Rename folder Переименовать папку - + Rename files... Переименовать файлы... - + Organize into folders... Разложить по папкам... - + Rescan library for XML info Повторное сканирование библиотеки для получения информации XML - + Set as uncompleted Отметить как не завершено - + Set as completed Отметить как завершено - + Set as read Отметить как прочитано - - + + Set as unread Отметить как не прочитано - + Set custom cover - Установить собственную обложку + Установить пользовательскую обложку - - + + Delete custom cover Удалить пользовательскую обложку @@ -2197,27 +3093,73 @@ Missing files: %3 Последний день - - Last %1 days - Последние %1 дней + + Last %1 days + Последние %1 дней + + + + 1 sublist + 1 вложенный список + + + + %1 sublists + %1 вложенных списков + + + + LocalComicListModel + + + file name + имя файла + + + + MoveToFolderDialog + + + Move to folder + Переместить в папку + + + + Top level + Верхний уровень + + + + Move "%1" to: + Переместить «%1» в: + + + + NewListFolderDialog + + + New folder + Новая папка + + + + Reading lists + Списки чтения - - 1 sublist - 1 вложенный список + + Smart lists + Умные списки - - %1 sublists - %1 вложенных списков + + Folder name: + Имя папки: - - - LocalComicListModel - - file name - имя файла + + For: + Для: @@ -2234,8 +3176,8 @@ Missing files: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>Вы можете создать библиотеку в любой папке, YACReaderLibrary будет импортировать все комиксы и папки из этой папки. Если вы уже ранее создавали библиотеки, их можно будет открыть.< / p > <p>Не забывайте, что Вы можете использовать YACReader в качестве отдельного приложения для чтения комиксов на вашем компьютере.</п> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Вы можете создать библиотеку в любой папке, YACReaderLibrary будет импортировать все комиксы и папки из этой папки. Если вы уже ранее создавали библиотеки, их можно будет открыть.</p><p>Не забывайте, что вы можете использовать YACReader в качестве отдельного приложения для чтения комиксов на вашем компьютере.</p> @@ -2255,7 +3197,7 @@ Missing files: %3 OptionsDialog - Restore defautls + Restore defaults Вернуть к первоначальным значениям @@ -2281,12 +3223,12 @@ Missing files: %3 Comic Vine API key - Comic Vine API ключ + Ключ API Comic Vine Edit Comic Vine API key - Редактировать Comic Vine API ключ + Редактировать ключ API Comic Vine @@ -2301,7 +3243,7 @@ Missing files: %3 Use selected comic cover as background - Обложка комикса фоновое изображение + Использовать обложку выбранного комикса как фон @@ -2317,12 +3259,12 @@ Missing files: %3 Grid view - Фоновое изображение + Вид сеткой Appearance - Появление + Внешний вид @@ -2347,12 +3289,12 @@ Missing files: %3 Close to tray - Рядом с лотком + Закрывать в трей - Start into the system tray - Запустите в системном трее + Start in the system tray + Запускать в системном трее @@ -2363,22 +3305,22 @@ Missing files: %3 Import metadata from ComicInfo.xml when adding new comics Import metada from ComicInfo.xml when adding new comics - Импортируйте метаданные из ComicInfo.xml при добавлении новых комиксов. + Импортировать метаданные из ComicInfo.xml при добавлении новых комиксов Consider 'recent' items added or updated since X days ago - Учитывайте «недавние» элементы, добавленные или обновленные X дней назад. + Считать «недавними» элементы, добавленные или обновленные X дней назад Third party reader - Сторонний читатель + Сторонняя программа чтения Write {comic_file_path} where the path should go in the command - Напишите {comic_file_path}, где должен идти путь в команде. + Укажите {comic_file_path} на месте пути в команде @@ -2393,12 +3335,12 @@ Missing files: %3 Try to detect changes automatically - Попробуйте обнаружить изменения автоматически + Пытаться автоматически обнаруживать изменения Update libraries periodically - Периодически обновляйте библиотеки + Периодически обновлять библиотеки @@ -2452,10 +3394,10 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2472,12 +3414,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Compare the modified date of files when updating a library (not recommended) - Сравните дату изменения файлов при обновлении библиотеки (не рекомендуется) + Сравнивать дату изменения файлов при обновлении библиотеки (не рекомендуется) Display continue reading banner - Отображение баннера продолжения чтения + Отображать баннер продолжения чтения @@ -2507,18 +3449,99 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Restart is needed - Требуется перезагрузка + Требуется перезапуск OrganizeFiles - + + + Unknown Series + Неизвестная серия + + + + series + серия + + + + Unknown Publisher + Неизвестный издатель + + + + publisher + издатель + + + + title + название + + + + Unknown Comic + Неизвестный комикс + + + + Series #Number - Title + Серия №Номер - Название + + + + Series #Number + Серия №Номер + + + + Series #Number (of Count) + Серия №Номер (из Всего) + + + + Number - Title + Номер - Название + + + + Series #Number (Year) + Серия №Номер (Год) + + + + Publisher / Series / Series #Number - Title + Издатель / Серия / Серия №Номер - Название + + + + Publisher / Imprint / Series / Series #Number - Title + Издатель / Импринт / Серия / Серия №Номер - Название + + + + Series / Series #Number + Серия / Серия №Номер + + + + Publisher / Series / Series #Number (Year) + Издатель / Серия / Серия №Номер (Год) + + + + Series / original file name + Серия / исходное имя файла + + + Renamed, %1 is already in use Переименовано, имя %1 уже занято - + Missing metadata: %1 Отсутствуют метаданные: %1 @@ -2527,62 +3550,82 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %1 could not be created Не удалось создать %1 + + + %1 (copy also failed: %2) + %1 (копирование также не удалось: %2) + + + + the copy did not have the same size as the original + размер копии отличается от оригинала + + + + the original could not be deleted after it was copied + не удалось удалить оригинал после копирования + + + + The destination folder could not be created. + Не удалось создать папку назначения. + + + + %1 (the file was left at %2) + %1 (файл остался в %2) + OrganizeFilesCoordinator - - + + Organize files Упорядочить файлы - + This folder does not contain any comics. В этой папке нет комиксов. - - This library is busy: %1 - Эта библиотека занята: %1 - - - + the library database could not be opened не удалось открыть базу данных библиотеки - + the library database could not be locked for writing не удалось заблокировать базу данных библиотеки для записи - + a folder entry could not be restored не удалось восстановить запись о папке - + a comic entry could not be updated не удалось обновить запись о комиксе - + the library database could not be saved: %1 не удалось сохранить базу данных библиотеки: %1 - + the record of the last organize run could not be read не удалось прочитать запись о последней операции упорядочивания - + the folder %1 could not be created не удалось создать папку %1 - + %n file(s) could not be moved back не удалось вернуть на место %n файл @@ -2593,10 +3636,6 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFilesDialog - - Format: - Формат: - Organize files @@ -2728,19 +3767,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Cancel Отмена - - Copy the list - Скопировать список - Undo Отменить - - Close - Закрыть - Copy failure details @@ -3174,276 +4205,391 @@ To stop an automatic update tap on the loading indicator next to the Libraries t PropertiesDialog - + Day: День: - + Plot Сюжет - + Size: Размер: - + Year: Год: - + Inker(s): Контуровщик(и): - + Publishing - Издатели + Публикация - + Publisher: Издатель: - + General info Общая информация - + Color/BW: - Цвет/BW: + Цвет/Ч/Б: - + Edit selected comics information Редактировать информацию выбранного комикса - + Penciller(s): Художник(и): - + Colorist(s): Колорист(ы): - + Genre: Жанр: - + Notes Примечания - + Load previous page as cover Загрузить предыдущую страницу в качестве обложки - + Load next page as cover Загрузить следующую страницу в качестве обложки - + Reset cover to the default image Сбросить обложку к изображению по умолчанию - + Load custom cover image - Загрузить собственное изображение обложки + Загрузить пользовательское изображение обложки - + Series: Серия: - + + e.g. 1-5, 8, 0.5, Annual 1 + напр. 1-5, 8, 0.5, Annual 1 + + + + Issue number: - Номер выпуска + Номер выпуска: - + alt. number: - альт. число: + альт. номер: - + Alternate series: - Альтернативный сериал: + Альтернативная серия: - + Series Group: Группа серий: - + Editor(s): Редактор(ы): - + Imprint: - Выходные данные: + Импринт: - + Month: Месяц: - + Type: Тип: - + Language (ISO): Язык (ISO): - + Teams: Команды: - + Locations: - Местоположение: + Локации: - + Main character or team: Главный герой или команда: - + Review: - Обзор: + Рецензия: - + Notes: - Заметки: + Примечания: + + + + + Track missing issues in "%1" + Отслеживать недостающие выпуски в «%1» + + + + Issues: + Выпуски: + + + + Track + Отслеживать + + + + At most %1 issues can be tracked at once. + Можно отслеживать не более %1 выпусков за один раз. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Проверьте выпуски: диапазон должен идти по возрастанию, а части между запятыми не могут быть пустыми. + + + + No new issues to track. + Нет новых выпусков для отслеживания. + + + + %n issue(s) will be tracked: %1. + + Будет отслеживаться %n выпуск: %1. + Будут отслеживаться %n выпуска: %1. + Будет отслеживаться %n выпусков: %1. + + + + + That is a lot of issues; check the numbers. + Выпусков много; проверьте номера. + + + + %n issue(s) already in the folder. + + %n выпуск уже в папке. + %n выпуска уже в папке. + %n выпусков уже в папке. + + + + + %n repeated issue(s). + + %n повторный выпуск. + %n повторных выпуска. + %n повторных выпусков. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Неотслеживаемые выпуски + + + + The missing issues could not be tracked. + Не удалось начать отслеживание недостающих выпусков. - + + Comic skipped + Комикс пропущен + + + + A comic was removed from the library or now has a different file, so it is skipped. + Комикс был удалён из библиотеки или теперь связан с другим файлом, поэтому он пропущен. + + + + + Changes not saved + Изменения не сохранены + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Редактируемый комикс был удалён из библиотеки или теперь связан с другим файлом. Ваши изменения не сохранены. + + + + The changes could not be saved. + Не удалось сохранить изменения. + + + Invalid cover - Неверное покрытие + Неверная обложка - + The image is invalid. Изображение недействительно. - + Synopsis: Описание: - + Title: - Заголовок: + Название: - + Not found Не найдено - + Characters: Персонажи: - + Authors Авторы - + Age rating: Возрастной рейтинг: - + Story arc: Сюжетная арка: - + Writer(s): - Писатель(и): + Сценарист(ы): - + Tags: Теги: - + Comic not found. You should update your library. Комикс не найден. Обновите вашу библиотеку. - + Edit comic information Редактировать информацию комикса - + Cover page Страница обложки - + Cover Artist(s): - Художник(и) Обложки: + Художник(и) обложки: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - <a style='color: ##666666; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> Открыть страницу этого комикса на сайте Comic Vine </a> + Ссылка на Comic Vine: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> открыть </a> - + Volume: - Объём : + Том: - + Format: Формат: - + Letterer(s): - Гравёр-шрифтовик(и): + Леттерер(ы): - - + + of: из: - + Arc number: - Номер дуги: + Номер арки: @@ -3456,15 +4602,15 @@ To stop an automatic update tap on the loading indicator next to the Libraries t unable to load 7z lib from ./utils - Не удается загрузить библиотеку распаковщика 7z из ./utils + не удается загрузить библиотеку 7z из ./utils - + Select custom cover - Выбрать индивидуальную обложку + Выбрать пользовательскую обложку - + Images (%1) Изображения (%1) @@ -3497,31 +4643,168 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists Добавить новый список чтения - - + + List name: Имя списка: - + + Import reading lists + Импорт списков чтения + + + + Reading lists (*.cbl *.json);;All files (*) + Списки чтения (*.cbl *.json);;Все файлы (*) + + + + New smart list + Новый умный список + + + + + The smart list could not be saved. + Не удалось сохранить умный список. + + + + Edit smart list + Изменить умный список + + + + Delete folder + Удалить папку + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + Удалить папку «%1» и всё её содержимое? Её папки и списки будут удалены, а файлы комиксов сохранятся. + + + Delete list/label Удалить список/ярлык - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - Выбранные элементы будут удалены, ваши комиксы или папки НЕ БУДУТ удалены с вашего жёсткого диска. Вы уверены? + Выбранный элемент будет удалён, ваши комиксы или папки НЕ БУДУТ удалены с вашего жёсткого диска. Вы уверены? - + Rename list name Изменить имя списка + + + Rename folder + Переименовать папку + + + + + Folder name: + Имя папки: + + + + + + New folder + Новая папка + + + + + The folder could not be created. + Не удалось создать папку. + + + + Reading list + Список чтения + + + + CBL reading list (*.cbl) + Список чтения CBL (*.cbl) + + + + JSON reading list (*.json) + Список чтения JSON (*.json) + + + + Web page (*.html) + Веб-страница (*.html) + + + + + Export reading list + Экспорт списка чтения + + + + Exporting reading list... + Экспорт списка чтения... + + + + The file could not be written. + Не удалось записать файл. + + + + Move to folder + Переместить в папку + + + + "%1" could not be moved. + Не удалось переместить «%1». + + + + ReadingListPage + + + %n comic(s) + + %n комикс + %n комикса + %n комиксов + + + + + %n missing + + %n отсутствует + %n отсутствуют + %n отсутствуют + + + + + Missing + Отсутствует + + + + Exported from YACReader on %1 + Экспортировано из YACReader %1 + RenameLibraryDialog @@ -3542,16 +4825,16 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - New Library Name : - Новое имя библиотеки: + New Library Name: + Новое имя библиотеки: ScraperResultsPaginator - Number of %1 found : %2 - Количество из %1 найдено : %2 + Number of %1 found: %2 + Найдено %1: %2 @@ -3561,8 +4844,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of volumes found : %1 - Количество найденных томов : %1 + Number of volumes found: %1 + Количество найденных томов: %1 @@ -3625,7 +4908,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Textual tags - Текстовые метки + Текстовые теги @@ -3690,7 +4973,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Locations appearing in the comic - Места, появляющиеся в комиксе + Локации, появляющиеся в комиксе @@ -3844,7 +5127,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Please provide some additional information for this comic. Please provide some additional information. - Пожалуйста, введите инфомарцию для поиска. + Пожалуйста, введите дополнительную информацию для этого комикса. @@ -3924,6 +5207,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. Просто начните вводить текст. Обычный текст ищется во всех метаданных. + + + %1 -> search for `%1` in any database field + %1 -> ????? `%1` ? ????? ???? ???? ?????? + 1. Search everywhere @@ -4019,7 +5307,9 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - Советы: пробелы работают как AND, а поиск не учитывает регистр. Используйте кавычки, чтобы включить пробелы в значение. + Советы: +Пробелы работают как AND, а поиск не учитывает регистр. +Используйте кавычки, чтобы включить пробелы в значение. @@ -4114,7 +5404,7 @@ Use quotes to include spaces in a value. Search textual tags - Поиск по текстовым меткам + Поиск по текстовым тегам @@ -4157,7 +5447,7 @@ Use quotes to include spaces in a value. Please provide some additional information. - Пожалуйста, введите инфомарцию для поиска. + Пожалуйста, введите дополнительную информацию. @@ -4194,8 +5484,8 @@ Use quotes to include spaces in a value. - Please, select the right comic info. - Пожалуйста, выберите правильную информацию об комиксе. + Please select the right comic info. + Пожалуйста, выберите правильную информацию о комиксе. @@ -4207,7 +5497,7 @@ Use quotes to include spaces in a value. - Please, select the right series for your comic. + Please select the right series for your comic. Пожалуйста, выберите правильную серию для вашего комикса. @@ -4241,54 +5531,59 @@ Use quotes to include spaces in a value. Unknown - Неизвестно + Неизвестно %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 неизвестно) %1 series - + Серий: %1 - + %1 comics selected %n comic(s) selected - %1 было выбрано + Выбрано комиксов: %1 - + Read - Прочитано + Прочитано - + In progress - В процессе + В процессе - + Unread - Непрочитанные + Непрочитанные - + Total pages - + Всего страниц - + Total size - + Общий размер + + + + Missing + Отсутствует - + Series - Ряд + Серия @@ -4305,8 +5600,8 @@ Use quotes to include spaces in a value. - You are trying to get information for various comics at once, are they part of the same series? - Вы пытаетесь получить информацию для нескольких комиксов одновременно, являются ли они все частью одной серии? + You are trying to get information for various comics at once. Are they part of the same series? + Вы пытаетесь получить информацию для нескольких комиксов одновременно. Являются ли они все частью одной серии? @@ -4363,57 +5658,74 @@ Use quotes to include spaces in a value. YACReader доступен для iOS и Android. Установите его для <a href='https://ios.yacreader.com'>iOS</a> или <a href='https://android.yacreader.com'>Android</a>. - enable the server - активировать сервер + + Set port + set port + Установить порт + + + SmartListDialog - Server connectivity information - Информация о подключении + + New smart list + Новый умный список - Scan it! - Сканируйте! + + Edit smart list + Изменить умный список - - Set port - set port - Установить порт + + Search filters and syntax + Фильтры и синтаксис поиска - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader доступен для устройств iOS и Android.<br/>Найдите его для <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> или <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Name: + Название: - Choose an IP address - Выбрать IP адрес + + Search: + Поиск: SortVolumeComics - + remove selected comics удалить выбранные комиксы - + sort comics to match comic information сортировать комиксы, чтобы соответствовать информации комиксов - + restore all removed comics восстановить все удаленные комиксы + + + Add the %n other issue(s) of this volume as missing comics + + Добавить %n другой выпуск этого тома как отсутствующий комикс + Добавить %n других выпуска этого тома как отсутствующие комиксы + Добавить %n других выпусков этого тома как отсутствующие комиксы + + - + issues - выпуск + выпуски - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. Пожалуйста, отсортируйте список комиксов слева, пока он не будет соответствовать информации комикса. @@ -4437,7 +5749,7 @@ Use quotes to include spaces in a value. i - я + i @@ -4452,7 +5764,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Удерживайте, чтобы выбранное значение мигало в пользовательском интерфейсе (пурпурный / переключено / 0↔10). Релизы восстанавливают оригинал. + Удерживайте, чтобы выбранное значение мигало в интерфейсе (пурпурный / переключено / 0↔10). При отпускании восстанавливается исходное значение. @@ -4462,17 +5774,17 @@ Use quotes to include spaces in a value. Light - Осветить + Светлая Dark - Темный + Темная ID: - ИДЕНТИФИКАТОР: + ID: @@ -4497,12 +5809,12 @@ Use quotes to include spaces in a value. Value - Ценить + Значение Save and apply - Сохраните и примените + Сохранить и применить @@ -4532,7 +5844,7 @@ Use quotes to include spaces in a value. true - истинный + true @@ -4540,7 +5852,7 @@ Use quotes to include spaces in a value. false - ЛОЖЬ + false @@ -4573,7 +5885,7 @@ Use quotes to include spaces in a value. Save failed - Сохранить не удалось + Сохранение не удалось @@ -4636,10 +5948,18 @@ Use quotes to include spaces in a value. - Updating.... + Updating... Обновление... + + ViewSelector + + + View + + + VolumeComicsModel @@ -4658,7 +5978,7 @@ Use quotes to include spaces in a value. issues - выпуск + выпуски @@ -4706,7 +6026,7 @@ Use quotes to include spaces in a value. Custom: - Пользовательский: + Пользовательская: @@ -4721,17 +6041,17 @@ Use quotes to include spaces in a value. Cover gap - Охватить разрыв + Зазор между обложками Central gap - Сфокусировать разрыв + Центральный зазор Zoom - Масштабировать + Zoom @@ -4746,17 +6066,17 @@ Use quotes to include spaces in a value. Cover Angle - Охватить угол + Угол обложек Visibility - Прозрачность + Видимость Light - Осветить + Освещение @@ -4775,8 +6095,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - Использовать VSync (повысить качество изображения в полноэкранном режиме , хуже производительность) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + Использовать VSync (улучшает качество изображения в полноэкранном режиме, снижает производительность) @@ -4789,17 +6109,17 @@ Use quotes to include spaces in a value. &Restore - &Rмагазин + &Восстановить Systray - Систрей + Системный трей YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary продолжит работать в системном трее. Чтобы завершить работу программы, выберите <b>Выход</b> в контекстном меню значка на панели задач. + YACReaderLibrary продолжит работать в системном трее. Чтобы завершить работу программы, выберите <b>Выход</b> в контекстном меню значка в системном трее. @@ -4815,18 +6135,56 @@ Use quotes to include spaces in a value. Предыдущие версии + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + База данных библиотеки отсутствует. Восстановите резервную копию или создайте библиотеку заново перед повторным сканированием XML. + + + + Unable to load the archive backend: %1 + Не удалось загрузить модуль работы с архивами: %1 + + + + Unable to start an XML rescan transaction. + Не удалось начать транзакцию повторного сканирования XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Выбранной папки больше нет в этой библиотеке. Перезагрузите библиотеку перед повторным сканированием. + + + + Unable to read comics for the XML rescan. + Не удалось прочитать комиксы для повторного сканирования XML. + + + + Unable to commit XML metadata changes. + Не удалось зафиксировать изменения метаданных XML. + + + + Unable to update XML metadata for %1. + Не удалось обновить метаданные XML для %1. + + YACReaderFieldEdit Restore to default - Вернуть к первоначальным значениям + Восстановить значения по умолчанию - + Click to overwrite - Изменить + Щелкните, чтобы перезаписать @@ -4834,15 +6192,17 @@ Use quotes to include spaces in a value. Restore to default - Вернуть к первоначальным значениям + Восстановить значения по умолчанию + + - - - + + + Click to overwrite - Изменить + Щелкните, чтобы перезаписать @@ -4888,7 +6248,7 @@ Use quotes to include spaces in a value. type to search - Начать поиск + введите для поиска diff --git a/YACReaderLibrary/yacreaderlibrary_source.ts b/YACReaderLibrary/yacreaderlibrary_source.ts index ccd7af22d..109a16a3b 100644 --- a/YACReaderLibrary/yacreaderlibrary_source.ts +++ b/YACReaderLibrary/yacreaderlibrary_source.ts @@ -11,6 +11,66 @@ AddLabelDialog + + + red + + + + + orange + + + + + yellow + + + + + green + + + + + cyan + + + + + blue + + + + + violet + + + + + purple + + + + + pink + + + + + white + + + + + light + + + + + dark + + Label name: @@ -51,12 +111,12 @@ - Comics folder : + Comics folder: - Library name : + Library name: @@ -69,7 +129,7 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> @@ -91,114 +151,647 @@ - - System + + System + + + + + Light + + + + + Dark + + + + + Custom + + + + + Remove + + + + + Remove this user-imported theme + + + + + Light: + + + + + Dark: + + + + + Custom: + + + + + Import theme... + + + + + Theme + + + + + Theme editor + + + + + Open Theme Editor... + + + + + Theme editor error + + + + + The current theme JSON could not be loaded. + + + + + Import theme + + + + + JSON files (*.json);;All files (*) + + + + + Could not import theme from: +%1 + + + + + Could not import theme from: +%1 + +%2 + + + + + Import failed + + + + + CblEntriesView + + + Find comic › + + + + + CblEntryDetail + + + Series + + + + + Number + + + + + Volume + + + + + Year + + + + + Format + + + + + + ComicVine ID + + + + + series and number + + + + + series without the volume, and number + + + + + letters and digits of the series, and number + + + + + series with its year, and number + + + + + volume %1 + + + + + Comic in your library + + + + + Missing in your library + + + + + Changed by you. + + + + + The entry has this comic. + + + + + Matched by %1. + + + + + Your library already has this comic as missing. Matched by %1. + + + + + Possible comic %1 + + + + + + Changed by you: it is imported as missing. + + + + + The comics have the same ComicVine ID. + + + + + The comic has the same series and number, but its volume or year is different. + + + + + The comics have the same series and number. + + + + + Values that are different from the file are in amber. + + + + + Closest series in your library + + + + + Library + + + + + Has %1 + + + + + No comic found. Tried: %1. + + + + + In the file + + + + + Entry %1 of %2 + + + + + Use this comic + + + + + Use this missing comic + + + + + CblFindComicPage + + + ‹ All entries + + + + + Previous problem + + + + + Next problem + + + + + Folders + + + + + Search the whole library + + + + + File + + + + + Series + + + + + No. + + + + + Volume + + + + + Year + + + + + Go to the next problem after I choose + + + + + Use this comic + + + + + Cancel + + + + + Search in this folder + + + + + Entry %1 of %2 + + + + + Missing: no comic found. Browse or search the library. + + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + + + + + + + Browse or search the library to choose another comic. + + + + + Problem %1 of %2 + + + + + %n problem(s) + + + + + + + + + + Library + + + + + Search results + + + + + CblImportDialog + + + Match to library comics — %1 + + + + + Apply + + + + + Close + + + + + Skip + + + + + Import + + + + + &Name: + + + + + A reading list with this name already exists. Import creates another one. + + + + + Filter entries + + + + + Matching the books of %1 with your library... + + + + + Matching the entries of %1 with your library... + + + + + + Match to library comics + + + + + This also gives comics to entries in %n other list(s): %1 + + + + + + + + The changes could not be applied. Nothing was changed. + + + + + The file cannot be opened. + + + + + The file is damaged (line %1, column %2). + + + + + The file is not a reading list. + + + + + The library cannot be read. + + + + + Import reading lists + + + + + The reading list could not be imported. Nothing was written. + + + + + Import reading lists — %1 (%2 of %3) + + + + + Import reading lists — %1 + + + + + All (%1) + + + + + Matched (%1) + + + + + Ambiguous (%1) - - Light + + Missing (%1) - - Dark + + Changed by you (%1) + + + %1 of %n have a comic + + + + + - - Custom + + Find comic... - - Remove + + Use the original match - - Remove this user-imported theme + + Make missing - - Light: + + Import as missing + + + CblImportModel - - Dark: + + Matched - - Custom: + + Ambiguous - - Import theme... + + Missing - - Theme + + Changed - - Theme editor + + # - - Open Theme Editor... + + In the file - - Theme editor error + + Comic in your library - - The current theme JSON could not be loaded. + + Status - - Import theme + + Already missing in your library + + + %n possible comic(s) + + + + + - - JSON files (*.json);;All files (*) + + Not in library + + + CblMatchComicDialog - - Could not import theme from: -%1 + + Match to library comic - - Could not import theme from: -%1 - -%2 + + Loading the library... - - Import failed + + The library cannot be read. ClassicComicsView - + Hide comic flow @@ -206,82 +799,82 @@ ComicInfoView - + Characters - + Main character or team - + Teams - + Locations - + Authors - + writer - + penciller - + inker - + colorist - + letterer - + cover artist - + editor - + imprint - + Publisher - + color - + b/w @@ -289,28 +882,28 @@ ComicManagementCoordinator - - + + YACReader not found - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. - + YACReader not found. There might be a problem with your YACReader installation. - + Error - + Error opening comic with third party reader. @@ -318,67 +911,67 @@ ComicModel - + yes - + no - + Title - + File Name - + Pages - + Size - + Read - + Current Page - + Publication Date - + Rating - + Series - + Volume - + Story Arc @@ -386,66 +979,110 @@ ComicVineDialog - + skip - + back - + next - + search - + close - - - + + + Looking for volume... - - + + comic %1 of %2 - %3 - + %1 comics selected - - Error connecting to ComicVine + + Error connecting to Comic Vine + + + + + + Retrieving tags for: %1 + + + + + Creating missing comic %1 of %2 + + + + + Comic Vine did not answer for %n comic(s). + + + + + + + + %n missing comic(s) got only the basic data of the volume. + + + + + + + + %n missing comic(s) have no cover. + + + + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + + + + + Try again later for the comics that were not updated. - - - Retrieving tags for : %1 + + Comic Vine - + Retrieving volume info... - + Looking for comic... @@ -477,17 +1114,17 @@ - Comics folder : + Comics folder: - Library Name : + Library Name: - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. @@ -504,11 +1141,19 @@ DBHelper - + The folder entry could not be found in the library database. + + DelayedProgressDialog + + + Cancel + + + EditShortcutsDialog @@ -518,7 +1163,7 @@ - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. @@ -533,7 +1178,7 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function @@ -566,6 +1211,14 @@ + + EmptyListFolderWidget + + + This folder is empty + + + EmptyReadingListWidget @@ -606,7 +1259,7 @@ - Output file : + Output file: @@ -644,7 +1297,7 @@ - Output folder : + Output folder: @@ -671,23 +1324,23 @@ FileComic - + 7z not found - + CRC error on page (%1): some of the pages will not be displayed correctly - + Unknown error opening the file - - + + Format not supported @@ -766,37 +1419,37 @@ GridComicsView - + Show info - + Manga - + Western manga - + Web comic - + Yonkoma - + Comic - + Unknown @@ -838,7 +1491,7 @@ - Info database location : + Info database location: @@ -856,7 +1509,7 @@ ImportLibraryDialog - Destination folder : + Destination folder: @@ -871,17 +1524,17 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) - Package location : + Package location: - Library Name : + Library Name: @@ -909,7 +1562,7 @@ - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> @@ -919,7 +1572,7 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> @@ -939,7 +1592,7 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> @@ -948,6 +1601,36 @@ + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + + + + + + Unable to start a library database transaction: %1 + + + + + + Unable to commit the library database: %1 + + + LibraryInfoView @@ -971,98 +1654,191 @@ + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + + + + + Retry + + + + + Recover and retry + + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + + + + + Another maintenance operation is using this library (%1)%2 + + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + + + LibraryManagementCoordinator - + Error opening the library - + Error creating the library - + Error updating the library + + + + XML information rescan + + + + + Restore recovery + + + + + Library upgrade + + + + + Library maintenance + + + + + Recover library access + + LibraryWindow - - Do you want remove + + Do you want to remove the library '%1'? - + YACReader Library - + Are you sure? + Delete folder - + Upgrade failed - + There were errors during library upgrade in: - + Restore recovery failed - + Update needed - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - + Download new version - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - + Library not available - + Library '%1' is no longer available. Do you want to remove it? - + Old library - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? @@ -1082,7 +1858,7 @@ - Please, select a folder first + Please select a folder first @@ -1101,84 +1877,113 @@ - + Unable to delete - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - - Rename or organize files + + Edit smart list... - - Set the type of the selected comics + + Delete smart list - - Search filters + + + + New folder... - - Unread + + + + Move to folder... - - In progress + + Rename folder... - - Highly rated + + Match to library comics... - - Recently added + + Track missing issues... + + + + + Export reading list... + + + + + Rename or organize files - Search syntax… + Set the type of the selected comics + + + + + Save as smart list… + + + + + Search filters + + + + + Unread - - A repair of this library is already running (%1). Wait for it to finish. + + In progress - - The library is locked by a repair that did not finish. + + Highly rated - - The library is locked by a repair started by %1. + + Recently added - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? + + Search syntax… - + Package operation failed - + The covers package operation could not be completed. @@ -1227,145 +2032,145 @@ Folder: %1 - + Save covers - + You are adding too many libraries. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. - + Library not found - + The selected folder doesn't contain any library. - - + + YACReader library database (*.ydb) - + The library database backup was created at: %1 - + Unable to create the library database backup: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? - + Restoring library database... - + The current library database is invalid. Restore the selected backup anyway? - - - The library maintenance lock may be stale. Remove it and retry? - - - - + Restart YACReaderLibrary before attempting recovery again. - + The library database was restored successfully. Update the library now? - + Library database damaged - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. - + Attempt repair - + Restore a backup... - + Repairing library database... - - + + + Library database repair - - Another maintenance operation is currently using this library. Try again after it finishes. + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + + + + + Check and repair - + The library database is already valid. - + Library database repaired - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 - + Library database rebuilt - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1373,7 +2178,7 @@ Update the library now? - + The damaged original was preserved at: @@ -1381,12 +2186,12 @@ The damaged original was preserved at: - + Library database repair failed - + The library database could not be repaired: %1%2 @@ -1394,103 +2199,139 @@ You can restore a backup from the Library menu or recreate the library. - - library? - - - - + Remove and delete metadata and backups - + Library info - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. - + Assign comics numbers - - Assign numbers starting in: + + Assign numbers starting at: + + + + + + Invalid image + + + + + + The selected file is not a valid image. + + + + + + + Error saving cover + + + + + + + There was an error saving the cover image. + + + + + + Match to library comic + + + + + This also gives the comic to entries in %n other list(s): %1 + + + + + + + + The comic could not be matched. Nothing was changed. - - - Invalid image + + The selected missing comics will be removed from this folder. Are you sure? - - - The selected file is not a valid image. + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? - - - Error saving cover + + Delete comics - - - There was an error saving the cover image. + + All the selected comics will be deleted from your disk. Are you sure? - - Delete comics + + Remove the comics from Reading? Their reading progress will be reset. - - All the selected comics will be deleted from your disk. Are you sure? + + The comics will be removed from Favorites. Are you sure? - + Remove comics - + Comics will only be deleted from the current label/list. Are you sure? - + Library name already exists - + There is another library with the name '%1'. - + Repaired: %1 Failed: %2 Missing files: %3 - + Copying comics... - + Moving comics... @@ -1571,429 +2412,504 @@ Missing files: %3 + Repair library database... + + + + + Check the current library database and attempt to repair any damage + + + + + Recover library access... + + + + + Recover access after an interrupted library maintenance operation + + + + Repair covers and comic info - + Retry comics with missing covers or incomplete information - + Rename library - + Rename current library Renomear biblioteca atual - + Remove library - + Remove current library from your collection Remover biblioteca atual da sua coleção - + Rescan library for XML info - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. - + Open library folder... - + Open the root folder of the current library - + Show library info - + Show information about the current library - + Open current comic - - Open current comic on YACReader + + Open current comic in YACReader Abrir quadrinho atual no YACReader - + Save selected covers to... - + Save covers of the selected comics as JPG files - - + + Set as read - + Set comic as read - - + + Set as unread - + Set comic as unread - - + + manga - + Set issue as manga - - + + comic - + Set issue as normal - + western manga - + Set issue as western manga - - + + web comic - + Set issue as web comic - - + + yonkoma - + Set issue as yonkoma - + Show/Hide marks - + Show or hide read marks - + Show/Hide recent indicator - + Show or hide recent indicator - - + + Fullscreen mode on/off - + Help, About YACReader Ajuda, Sobre o YACReader - + Add new folder - + Add new folder to the current library - + Rename folder - + Rename the current folder on disk and in the library - + Delete folder - + Delete current folder from disk - + Select root node Selecionar raiz - + Expand all nodes Expandir todos - + Collapse all nodes - + Show options dialog Mostrar opções - + Show comics server options dialog - - + + Change between comics views - + + Grid view + + + + + Flow view + + + + + Info view + + + + Open folder... - - + + Rename files... Organize files - - + + Organize into folders... - + Set as uncompleted - + Set as completed - + Set custom cover - + Delete custom cover - + western manga (left to right) - + Open containing folder... Abrir a pasta contendo... - + Select all comics - + Edit - + Assign current order to comics - + Update cover - + Delete selected comics - + Delete metadata from selected comics - + Download tags from Comic Vine - + Focus search line - + Focus comics view - + Edit shortcuts - + &Quit - + Update folder - + Update current folder - + Scan legacy XML metadata - - Add new reading list + + New reading list - + Add a new reading list to the current library - + + New smart list... + + + + + Add a saved search to the current library + + + + + New folder... + + + + + Add a folder for reading lists or smart lists + + + + + Import reading lists... + + + + + Import CBL reading lists into the current library + + + + + New + + + + + Add or import reading lists + + + + Remove reading list - + Remove current reading list from the library - + Add new label - + Add a new label to this library - + Rename selected list - + Rename any selected labels or lists - + Add to... - + Favorites - + Add selected comics to favorites list - - + + Reset rating @@ -2022,91 +2938,104 @@ Missing files: %3 - 4koma (top to botom) + 4koma (top to bottom) - - - + + + Set type - + Library - + Folder - + Comic - + + Match to library comic... + + + + + + + + Track missing issues... + + + + Open folder... - + Update folder - + Rename folder - + Rename files... - + Organize into folders... - + Rescan library for XML info - + Set as uncompleted - + Set as completed - + Set as read - - + + Set as unread - + Set custom cover - - + + Delete custom cover @@ -2147,11 +3076,57 @@ Missing files: %3 LocalComicListModel - + file name + + MoveToFolderDialog + + + Move to folder + + + + + Top level + + + + + Move "%1" to: + + + + + NewListFolderDialog + + + New folder + + + + + Reading lists + + + + + Smart lists + + + + + Folder name: + + + + + For: + + + NoLibrariesWidget @@ -2161,7 +3136,7 @@ Missing files: %3 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> @@ -2212,7 +3187,7 @@ Missing files: %3 - Start into the system tray + Start in the system tray @@ -2323,10 +3298,10 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2364,7 +3339,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Restore defautls + Restore defaults @@ -2403,115 +3378,216 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - - Comic Flow + + Comic Flow + + + + + + Libraries + + + + + Grid view + + + + + General + + + + + Appearance + + + + + Options + + + + + Restart is needed + + + + + OrganizeFiles + + + + Unknown Series + + + + + series + + + + + Unknown Publisher + + + + + publisher + + + + + title + + + + + Unknown Comic + + + + + Series #Number - Title + + + + + Series #Number + + + + + Series #Number (of Count) + + + + + Number - Title + + + + + Series #Number (Year) + + + + + Publisher / Series / Series #Number - Title + + + + + Publisher / Imprint / Series / Series #Number - Title + + + + + Series / Series #Number + + + + + Publisher / Series / Series #Number (Year) - - - Libraries + + Series / original file name - - Grid view + + Renamed, %1 is already in use - - General + + Missing metadata: %1 - - Appearance + + %1 could not be created - - Options + + %1 (copy also failed: %2) - - Restart is needed + + the copy did not have the same size as the original - - - OrganizeFiles - - Renamed, %1 is already in use + + the original could not be deleted after it was copied - - Missing metadata: %1 + + The destination folder could not be created. - - %1 could not be created + + %1 (the file was left at %2) OrganizeFilesCoordinator - - + + Organize files - + This folder does not contain any comics. - - This library is busy: %1 - - - - + the library database could not be opened - + the library database could not be locked for writing - + a folder entry could not be restored - + a comic entry could not be updated - + the library database could not be saved: %1 - + the record of the last organize run could not be read - + the folder %1 could not be created - + %n file(s) could not be moved back @@ -3073,274 +4149,386 @@ To stop an automatic update tap on the loading indicator next to the Libraries t PropertiesDialog - + General info - + Authors - + Publishing - + Plot - + Notes - + Cover page - + Load previous page as cover - + Load next page as cover - + Reset cover to the default image - + Load custom cover image - + Series: - + Title: - - + + e.g. 1-5, 8, 0.5, Annual 1 + + + + + of: - + + Issue number: - + Volume: - + Arc number: - + Story arc: - + alt. number: - + Alternate series: - + Series Group: - + Genre: - + Size: - + Writer(s): - + Penciller(s): - + Inker(s): - + Colorist(s): - + Letterer(s): - + Cover Artist(s): - + Editor(s): - + Imprint: - + Day: - + Month: - + Year: - + Publisher: - + Format: - + Color/BW: - + Age rating: - + Type: - + Language (ISO): - + Synopsis: - + Characters: - + Teams: - + Locations: - + Main character or team: - + Review: - + Notes: - + Tags: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - + Not found - + Comic not found. You should update your library. - + Edit selected comics information - + + + Track missing issues in "%1" + + + + + Issues: + + + + + Track + + + + + At most %1 issues can be tracked at once. + + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + + + + + No new issues to track. + + + + + %n issue(s) will be tracked: %1. + + + + + + + + That is a lot of issues; check the numbers. + + + + + %n issue(s) already in the folder. + + + + + + + + %n repeated issue(s). + + + + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + + + + + The missing issues could not be tracked. + + + + + Comic skipped + + + + + A comic was removed from the library or now has a different file, so it is skipped. + + + + + + Changes not saved + + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + + + + + The changes could not be saved. + + + + Invalid cover - + The image is invalid. - + Edit comic information @@ -3358,12 +4546,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - + Select custom cover - + Images (%1) @@ -3396,31 +4584,166 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists - - + + List name: - + + Import reading lists + + + + + Reading lists (*.cbl *.json);;All files (*) + + + + + New smart list + + + + + + The smart list could not be saved. + + + + + Edit smart list + + + + + Delete folder + + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + + + + Delete list/label - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - + Rename list name + + + Rename folder + + + + + + Folder name: + + + + + + + New folder + + + + + + The folder could not be created. + + + + + Reading list + + + + + CBL reading list (*.cbl) + + + + + JSON reading list (*.json) + + + + + Web page (*.html) + + + + + + Export reading list + + + + + Exporting reading list... + + + + + The file could not be written. + + + + + Move to folder + + + + + "%1" could not be moved. + + + + + ReadingListPage + + + %n comic(s) + + + + + + + + %n missing + + + + + + + + Missing + + + + + Exported from YACReader on %1 + + RenameLibraryDialog @@ -3441,7 +4764,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - New Library Name : + New Library Name: @@ -3449,7 +4772,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ScraperResultsPaginator - Number of volumes found : %1 + Number of volumes found: %1 @@ -3460,7 +4783,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of %1 found : %2 + Number of %1 found: %2 @@ -3823,6 +5146,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. + + + %1 -> search for `%1` in any database field + + 1. Search everywhere @@ -4073,7 +5401,7 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. + Please select the right comic info. @@ -4101,7 +5429,7 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. @@ -4154,38 +5482,43 @@ Use quotes to include spaces in a value. - + %1 comics selected %n comic(s) selected - + Read - + In progress - + Unread - + Total pages - + Total size - + + Missing + + + + Series @@ -4194,7 +5527,7 @@ Use quotes to include spaces in a value. SeriesQuestion - You are trying to get information for various comics at once, are they part of the same series? + You are trying to get information for various comics at once. Are they part of the same series? @@ -4268,33 +5601,69 @@ Use quotes to include spaces in a value. + + SmartListDialog + + + New smart list + + + + + Edit smart list + + + + + Search filters and syntax + + + + + Name: + + + + + Search: + + + SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. - + sort comics to match comic information - + issues - + remove selected comics - + restore all removed comics + + + Add the %n other issue(s) of this volume as missing comics + + + + + ThemeEditorDialog @@ -4507,7 +5876,7 @@ Use quotes to include spaces in a value. - Updating.... + Updating... @@ -4516,6 +5885,14 @@ Use quotes to include spaces in a value. + + ViewSelector + + + View + + + VolumeComicsModel @@ -4651,7 +6028,7 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) + Use VSync (improves image quality in fullscreen mode, but reduces performance) @@ -4691,11 +6068,49 @@ Use quotes to include spaces in a value. + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + + + + + Unable to load the archive backend: %1 + + + + + Unable to start an XML rescan transaction. + + + + + The selected folder is no longer in this library. Reload the library before rescanning. + + + + + Unable to read comics for the XML rescan. + + + + + Unable to commit XML metadata changes. + + + + + Unable to update XML metadata for %1. + + + YACReaderFieldEdit - + Click to overwrite @@ -4708,10 +6123,12 @@ Use quotes to include spaces in a value. YACReaderFieldPlainTextEdit + + - - - + + + Click to overwrite diff --git a/YACReaderLibrary/yacreaderlibrary_tr.ts b/YACReaderLibrary/yacreaderlibrary_tr.ts index d5ff6a68f..9ead5b167 100644 --- a/YACReaderLibrary/yacreaderlibrary_tr.ts +++ b/YACReaderLibrary/yacreaderlibrary_tr.ts @@ -21,6 +21,66 @@ Label name: Etiket adı: + + + red + Kırmızı + + + + orange + Turuncu + + + + yellow + Sarı + + + + green + Yeşil + + + + cyan + Camgöbeği + + + + blue + Mavi + + + + violet + Menekşe + + + + purple + Mor + + + + pink + Pembe + + + + white + Beyaz + + + + light + Açık + + + + dark + Koyu + Choose a color: @@ -42,7 +102,7 @@ Add an existing library - Kütüphaneye ekle + Mevcut bir kütüphane ekle @@ -51,13 +111,13 @@ - Comics folder : - Çizgi roman klasörü : + Comics folder: + Çizgi roman klasörü: - Library name : - Kütüphane adı : + Library name: + Kütüphane adı: @@ -69,7 +129,7 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> Comic Vine'a bağlanmadan önce kendi API anahtarınıza ihtiyacınız var. Lütfen <a href="http://www.comicvine.com/api/">buradan</a> ücretsiz bir tane edinin @@ -98,22 +158,22 @@ Light - Işık + Açık Dark - Karanlık + Koyu Custom - Gelenek + Özel Remove - Kaldırmak + Kaldır @@ -123,17 +183,17 @@ Light: - Işık: + Açık: Dark: - Karanlık: + Koyu: Custom: - Kişisel: + Özel: @@ -163,7 +223,7 @@ The current theme JSON could not be loaded. - Geçerli tema JSON yüklenemedi. + Geçerli temanın JSON verisi yüklenemedi. @@ -200,103 +260,620 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - Comic Flow'u gizle + + Find comic › + Çizgi roman bul › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - Çizgi romanlar kopyalanıyor... + + Series + Seri - Moving comics... - Çizgi romanlar taşınıyor... + + Number + Sayı + + + + Volume + Cilt + + + + Year + Yıl + + + + Format + Biçim + + + + + ComicVine ID + Comic Vine kimliği + + + + series and number + seri ve sayı + + + + series without the volume, and number + cilt olmadan seri ve sayı + + + + letters and digits of the series, and number + serinin harfleri ve rakamları ile sayı + + + + series with its year, and number + yılıyla birlikte seri ve sayı + + + + volume %1 + cilt %1 + + + + Comic in your library + Kitaplığınızdaki çizgi roman + + + + Missing in your library + Kitaplığınızda eksik + + + + Changed by you. + Sizin tarafınızdan değiştirildi. + + + + The entry has this comic. + Bu kayıt bu çizgi romanla eşleştirilmiş. + + + + Matched by %1. + %1 ile eşleştirildi. + + + + Your library already has this comic as missing. Matched by %1. + Kitaplığınızda bu çizgi roman zaten eksik olarak kayıtlı. %1 ile eşleştirildi. + + + + Possible comic %1 + Olası çizgi roman %1 + + + + + Changed by you: it is imported as missing. + Sizin tarafınızdan değiştirildi: eksik olarak içe aktarılır. + + + + The comics have the same ComicVine ID. + Çizgi romanların Comic Vine kimliği aynı. + + + + The comic has the same series and number, but its volume or year is different. + Çizgi romanın serisi ve sayısı aynı, ancak cildi veya yılı farklı. + + + + The comics have the same series and number. + Çizgi romanların serisi ve sayısı aynı. + + + + Values that are different from the file are in amber. + Dosyadakinden farklı değerler kehribar rengindedir. + + + + Closest series in your library + Kitaplığınızdaki en yakın seriler + + + + Library + Kütüphane + + + + Has %1 + %1 var + + + + No comic found. Tried: %1. + Çizgi roman bulunamadı. Denenen: %1. + + + + In the file + Dosyada + + + + Entry %1 of %2 + Kayıt %1 / %2 + + + + Use this comic + Bu çizgi romanı kullan + + + + Use this missing comic + Bu eksik çizgi romanı kullan + + + + CblFindComicPage + + + ‹ All entries + ‹ Tüm kayıtlar + + + + Previous problem + Önceki sorun + + + + Next problem + Sonraki sorun + + + + Folders + Klasörler + + + + Search the whole library + Tüm kitaplıkta ara + + + + File + Dosya + + + + Series + Seri + + + + No. + No. + + + + Volume + Cilt + + + + Year + Yıl + + + + Go to the next problem after I choose + Seçimimden sonra sonraki soruna geç + + + + Use this comic + Bu çizgi romanı kullan + + + + Cancel + Vazgeç + + + + Search in this folder + Bu klasörde ara + + + + Entry %1 of %2 + Kayıt %1 / %2 + + + + Missing: no comic found. Browse or search the library. + Eksik: çizgi roman bulunamadı. Kitaplığa göz atın veya arayın. + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + Belirsiz: %n olası çizgi roman. Kitaplığa göz atın veya arayın. + + + + + Browse or search the library to choose another comic. + Başka bir çizgi roman seçmek için kitaplığa göz atın veya arayın. + + + + Problem %1 of %2 + Sorun %1 / %2 + + + + %n problem(s) + + %n sorun + + + + + + + Library + Kütüphane + + + + Search results + Arama sonuçları + + + + CblImportDialog + + + Match to library comics — %1 + Kitaplıktaki çizgi romanlarla eşleştir — %1 + + + + Apply + Uygula + + + + Close + Kapat + + + + Skip + Atla + + + + Import + İçe aktar + + + + &Name: + &Ad: + + + + A reading list with this name already exists. Import creates another one. + Bu adda bir okuma listesi zaten var. İçe aktarma başka bir liste oluşturur. + + + + Filter entries + Kayıtları filtrele + + + + Matching the books of %1 with your library... + %1 içindeki kitaplar kitaplığınızla eşleştiriliyor... + + + + Matching the entries of %1 with your library... + %1 içindeki kayıtlar kitaplığınızla eşleştiriliyor... + + + + + Match to library comics + Kitaplıktaki çizgi romanlarla eşleştir + + + + This also gives comics to entries in %n other list(s): %1 + + Bu işlem %n başka listedeki kayıtlara da çizgi roman atar: %1 + + + + + The changes could not be applied. Nothing was changed. + Değişiklikler uygulanamadı. Hiçbir şey değiştirilmedi. + + + + The file cannot be opened. + Dosya açılamıyor. + + + + The file is damaged (line %1, column %2). + Dosya hasarlı (satır %1, sütun %2). + + + + The file is not a reading list. + Dosya bir okuma listesi değil. + + + + The library cannot be read. + Kitaplık okunamıyor. + + + + Import reading lists + Okuma listelerini içe aktar + + + + The reading list could not be imported. Nothing was written. + Okuma listesi içe aktarılamadı. Hiçbir şey yazılmadı. + + + + Import reading lists — %1 (%2 of %3) + Okuma listelerini içe aktar — %1 (%2 / %3) + + + + Import reading lists — %1 + Okuma listelerini içe aktar — %1 + + + + All (%1) + Tümü (%1) + + + + Matched (%1) + Eşleşen (%1) + + + + Ambiguous (%1) + Belirsiz (%1) + + + + Missing (%1) + Eksik (%1) + + + + Changed by you (%1) + Sizin değiştirdikleriniz (%1) + + + + %1 of %n have a comic + + %n kayıttan %1 tanesinde çizgi roman var + + + + + Find comic... + Çizgi roman bul... + + + + Use the original match + İlk eşleşmeyi kullan + + + + Make missing + Eksik olarak işaretle + + + + Import as missing + Eksik olarak içe aktar + + + + CblImportModel + + + Matched + Eşleşti + + + + Ambiguous + Belirsiz + + + + Missing + Eksik + + + + Changed + Değiştirildi + + + + # + # + + + + In the file + Dosyada + + + + Comic in your library + Kitaplığınızdaki çizgi roman + + + + Status + Durum + + + + Already missing in your library + Kitaplığınızda zaten eksik + + + + %n possible comic(s) + + %n olası çizgi roman + + + + + Not in library + Kitaplıkta yok + + + + CblMatchComicDialog + + + Match to library comic + Kitaplıktaki çizgi romanla eşleştir + + + + Loading the library... + Kitaplık yükleniyor... + + + + The library cannot be read. + Kitaplık okunamıyor. + + + + ClassicComicsView + + + Hide comic flow + Comic Flow'u gizle ComicInfoView - + Main character or team Ana karakter veya takım - + Teams Takımlar - + Locations Konumlar - + Authors Yazarlar - + writer yazar - + penciller çizer - + inker mürekkepleyen - + colorist renklendiren - + letterer harflendiren - + cover artist kapak çizeri - + editor editör - + imprint yayın markası - + Publisher Yayınevi - + color renk - + b/w s/b - + Characters Karakterler @@ -304,28 +881,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader bulunamadı - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. YACReader bulunamadı. YACReader, YACReaderLibrary ile aynı klasöre kurulmalıdır. - + YACReader not found. There might be a problem with your YACReader installation. YACReader bulunamadı. YACReader kurulumunuzda bir sorun olabilir. - + Error Hata - + Error opening comic with third party reader. Çizgi roman üçüncü taraf okuyucuyla açılırken hata oluştu. @@ -333,136 +910,177 @@ ComicModel - + no hayır - + yes evet - + Read - Oku + Okundu - + Size Boyut - + Pages Sayfalar - + Title Başlık - + File Name Dosya Adı - + Current Page - Geçreli Sayfa + Geçerli Sayfa - + Publication Date Yayın Tarihi - + Rating - Reyting + Derecelendirme - + Series Seri - + Volume - Hacim + Cilt - + Story Arc - Hikaye Arkı + Hikâye Yayı ComicVineDialog - + skip geç - + back geri - + next sonraki - + search ara - + close kapat - - - + + + Looking for volume... - Sayılar aranıyor... + Cilt aranıyor... - - + + comic %1 of %2 - %3 çizgi roman %1 / %2 - %3 - + %1 comics selected %1 çizgi roman seçildi - - Error connecting to ComicVine - ComicVine sitesine bağlanılırken hata + + Error connecting to Comic Vine + Comic Vine sitesine bağlanılırken hata - - - Retrieving tags for : %1 + + + Retrieving tags for: %1 %1 için etiketler alınıyor - + + Creating missing comic %1 of %2 + Eksik çizgi roman oluşturuluyor: %1 / %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine %n çizgi roman için yanıt vermedi. + + + + + %n missing comic(s) got only the basic data of the volume. + + %n eksik çizgi roman yalnızca cildin temel verilerini aldı. + + + + + %n missing comic(s) have no cover. + + %n eksik çizgi romanın kapağı yok. + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine saatte yaklaşık 200 isteğe izin verir; güncellenmeyen çizgi romanlar için daha sonra yeniden deneyin. + + + + Try again later for the comics that were not updated. + Güncellenmeyen çizgi romanlar için daha sonra yeniden deneyin. + + + + Comic Vine + Comic Vine + + + Retrieving volume info... - Sayı bilgileri alınıyor... + Cilt bilgileri alınıyor... - + Looking for comic... - Çizgi romanlar aranıyor... + Çizgi roman aranıyor... @@ -492,38 +1110,46 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - Yeni kütüphanenin oluşturulması birkaç dakika sürecek. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + Kütüphane oluşturmak birkaç dakika sürebilir. İşlemi durdurabilir ve görevi tamamlamak için kütüphaneyi daha sonra güncelleyebilirsiniz. The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - Seçilen dizine yazma iznimiz yok yazma izni olduğundan emin ol + Seçilen yol mevcut değil veya geçerli bir yol değil. Bu klasöre yazma erişiminiz olduğundan emin olun - Comics folder : - Çizgi roman klasörü : + Comics folder: + Çizgi roman klasörü: - Library Name : - Kütüphane Adı : + Library Name: + Kütüphane Adı: Path not found - Dizin bulunamadı + Yol bulunamadı DBHelper - + The folder entry could not be found in the library database. Klasör kaydı kütüphane veritabanında bulunamadı. + + DelayedProgressDialog + + + Cancel + Vazgeç + + EditShortcutsDialog @@ -533,7 +1159,7 @@ - To change a shortcut, double click in the key combination and type the new keys. + To change a shortcut, double-click the key combination and type the new keys. Bir kısayolu değiştirmek için tuş kombinasyonuna çift tıklayın ve yeni tuşları girin. @@ -548,8 +1174,8 @@ - The shortcut "%1" is already assigned to other function - "%1" kısayalou zaten başka bir işlev tarafından kullanılıyor + The shortcut "%1" is already assigned to another function + "%1" kısayolu zaten başka bir işleve atanmış @@ -581,6 +1207,14 @@ Bu etiket henüz çizgi roman içermiyor + + EmptyListFolderWidget + + + This folder is empty + Bu klasör boş + + EmptyReadingListWidget @@ -599,7 +1233,7 @@ You are not reading anything yet, come on!! - Henüz bir şey okumuyorsun, hadi ama! + Henüz bir şey okumuyorsunuz, hadi ama! @@ -611,13 +1245,13 @@ ExportComicsInfoDialog - Output file : - Çıkış dosyası : + Output file: + Çıktı dosyası: Destination database name - Hedef adı + Hedef veritabanı adı @@ -632,17 +1266,17 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Seçilen dizine yazma iznimiz yok yazma izni olduğundan emin ol + Çıktı dosyası için seçilen yol mevcut değil veya geçerli bir yol değil. Bu klasöre yazma erişiminiz olduğundan emin olun Export comics info - Çizgi roman bilgilerini göster + Çizgi roman bilgilerini dışa aktar Problem found while writing - Yazma sırasında bir problem oldu + Yazma sırasında bir sorun bulundu @@ -655,22 +1289,22 @@ Create - Yeni bir tane yap + Oluştur The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - Seçilen konuma yeni bir kütüphane yazılamıyor + Çıktı dosyası için seçilen yol mevcut değil veya geçerli bir yol değil. Bu klasöre yazma erişiminiz olduğundan emin olun - Output folder : - Çıktı klasörü : + Output folder: + Çıktı klasörü: Problem found while writing - Yazım aşamasında bir problem bulundu + Yazma sırasında bir sorun bulundu @@ -680,38 +1314,31 @@ Destination directory - Hedef dizin + Hedef klasör FileComic - + 7z not found 7z bulunamadı - + CRC error on page (%1): some of the pages will not be displayed correctly - CRC hatası, sayfada (%1): bazı sayfalar düzgün görüntülenmeyecek + (%1). sayfada CRC hatası: bazı sayfalar düzgün görüntülenmeyecek - + Unknown error opening the file Dosya açılırken bilinmeyen hata - - + + Format not supported - Dosya biçimi desteklenmiyor - - - - FolderContentView - - Continue Reading... - Okumaya Devam Et... + Biçim desteklenmiyor @@ -739,7 +1366,7 @@ Read - Oku + Okundu @@ -788,53 +1415,37 @@ GridComicsView - + Show info Bilgi göster - Library - Kütüphane - - - Folder - Klasör - - - Favorites - Favoriler - - - Recently added - Yakın zamanda eklenen - - - + Manga Manga - + Western manga Batı mangası - + Web comic Web çizgi romanı - + Yonkoma Yonkoma - + Comic Çizgi roman - + Unknown Bilinmiyor @@ -872,17 +1483,17 @@ Import - Çıkart + İçe aktar - Info database location : - Bilgi veritabanı konumu : + Info database location: + Bilgi veritabanı konumu: Import comics info - Çizgi roman bilgilerini çıkart + Çizgi roman bilgilerini içe aktar @@ -894,8 +1505,8 @@ ImportLibraryDialog - Destination folder : - Hedef klasör : + Destination folder: + Hedef klasör: @@ -909,18 +1520,18 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) Sıkıştırılmış kütüphane kapakları (*.clc) - Package location : - Paket konumu : + Package location: + Paket konumu: - Library Name : - Kütüphane Adı : + Library Name: + Kütüphane Adı: @@ -938,52 +1549,82 @@ Importing comics - önemli çizgi romanlar + Çizgi romanlar içe aktarılıyor - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderKütüphane şu anda yeni bir kütüphane oluşturuyor</p><p>Kütüphanenin oluşturulması birkaç dakika alacak.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary şu anda yeni bir kütüphane oluşturuyor.</p><p>Kütüphane oluşturmak birkaç dakika sürebilir. İşlemi durdurabilir ve görevi tamamlamak için kütüphaneyi daha sonra güncelleyebilirsiniz.</p> Some of the comics being added... - Bazı çizgi romanlar önceden eklenmiş... + Bazı çizgi romanlar ekleniyor... Updating the library - Kütüphaneyi güncelle + Kütüphane güncelleniyor - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>Kütüphane güncelleniyor</p><p>Güncellemeyi daha sonra iptal edebilirsin.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>Geçerli kütüphane güncelleniyor. Daha hızlı güncellemeler için lütfen kütüphanelerinizi sık sık güncelleyin.</p><p>İşlemi durdurabilir ve bu kütüphaneyi daha sonra güncellemeye devam edebilirsiniz.</p> Upgrading the library - Kütüphane güncelleniyor + Kütüphane yükseltiliyor <p>The current library is being upgraded, please wait.</p> - <p>Mevcut kütüphane güncelleniyor, lütfen bekleyin.</p> + <p>Geçerli kütüphane yükseltiliyor, lütfen bekleyin.</p> Scanning the library - Kütüphaneyi taramak + Kütüphane taranıyor - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>Geçerli kitaplık, eski XML meta veri bilgileri için taranıyor.</p><p>Bu yalnızca bir kez gereklidir ve yalnızca kitaplığın YACReaderLibrary 9.8.2 veya daha eski bir sürümle oluşturulmuş olması durumunda gereklidir.</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>Geçerli kütüphane, eski XML meta veri bilgileri için taranıyor.</p><p>Bu yalnızca bir kez gereklidir ve yalnızca kütüphane YACReaderLibrary 9.8.2 veya daha eski bir sürümle oluşturulmuşsa gereklidir.</p> <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> - <p>Geçerli kitaplıkta eksik kapaklar ve tamamlanmamış çizgi roman bilgileri denetleniyor.</p><p>Bu işlem birkaç dakika sürebilir. İşlemi durdurup daha sonra yeniden çalıştırabilirsiniz.</p> + <p>Geçerli kütüphanede eksik kapaklar ve tamamlanmamış çizgi roman bilgileri denetleniyor.</p><p>Bu işlem birkaç dakika sürebilir. İşlemi durdurup daha sonra yeniden çalıştırabilirsiniz.</p> + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Kitaplık veri klasörü oluşturulamadı: %1. Klasör erişimini ve kullanılabilir disk alanını kontrol edin. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Kitaplık veri klasörü eksik: %1. Güncellemeden önce klasörü geri yükleyin veya kitaplığı yeniden oluşturun. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Kitaplık veritabanı eksik: %1. Güncellemeden önce bir yedeği geri yükleyin veya kitaplığı yeniden oluşturun. + + + + + Unable to start a library database transaction: %1 + Kitaplık veritabanı işlemi başlatılamadı: %1 + + + + + Unable to commit the library database: %1 + Kitaplık veritabanı işlemi kalıcılaştırılamadı: %1 @@ -1009,143 +1650,226 @@ Okunan çizgi roman sayısı + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + Bu kitaplık meşgul. Bakımın bitmesini bekleyin. Takılmış görünüyorsa Ayrıntılar bölümünde gösterilen uygulamayı kapatıp yeniden deneyin. + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + Önceki bir bakım işlemi bu kitaplığı serbest bırakmamış olabilir. Erişimi kurtarmadan önce başka bir bilgisayar da dahil olmak üzere hiçbir YACReaderLibrary veya YACReaderLibraryServer işleminin kitaplığı kullanmadığından emin olun. + + + + Retry + Yeniden dene + + + + Recover and retry + Kurtar ve yeniden dene + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Kitaplık kilidinin sahibi değişti. Erişimi kurtarmadan önce işlemi yeniden deneyip yeni sahibi inceleyin. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Bakım kilidi kaldırılamadı: %1. Dosyanın salt okunur olmadığından ve kitaplık veri klasörüne yazabildiğinizden emin olup yeniden deneyin. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Bakım kilidi dosyasının yolunu bir klasör kullanıyor: %1. Yeniden denemeden önce bu klasörü yeniden adlandırın veya taşıyın. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Kitaplık kilidi dosyası oluşturulamadı: %1. Kitaplık veri klasörünün yazma izinlerini kontrol edin. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Kitaplık veri klasörü eksik: %1. Güncellemeden önce klasörü geri yükleyin veya kitaplığı yeniden oluşturun. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + Kitaplığın sahibi doğrulanamayan bir bakım kilidi var (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Başka bir bakım işlemi bu kitaplığı kullanıyor (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Kitaplık kilidi dosyası oluşturulamadı: %1. Kullanılabilir disk alanını ve kitaplık veri klasörüne erişimi kontrol edin. + + LibraryManagementCoordinator - + Error opening the library - Haa kütüphanesini aç + Kütüphane açılırken hata oluştu - + Error creating the library - Kütüphane oluşturma sorunu + Kütüphane oluşturulurken hata oluştu - + Error updating the library - Kütüphane güncelleme sorunu + Kütüphane güncellenirken hata oluştu + + + + + XML information rescan + XML bilgilerini yeniden tara + + + + Restore recovery + Geri yükleme kurtarması + + + + Library upgrade + Kitaplık yükseltmesi + + + + Library maintenance + Kitaplık bakımı + + + + Recover library access + Kitaplık erişimini kurtar LibraryWindow - + The selected folder doesn't contain any library. - Seçilen dosya kütüphanede yok. + Seçilen klasör herhangi bir kütüphane içermiyor. - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - Bu kütüphane YACReaderKütüphabenin bir önceki versiyonun oluşturulmuş, güncellemeye ihtiyacın var. Şimdi güncellemek ister misin ? - - - Remove and delete metadata - Metadata'yı kaldır ve sil + Bu kütüphane YACReaderLibrary'nin önceki bir sürümüyle oluşturulmuş. Güncellenmesi gerekiyor. Şimdi güncellensin mi? - + Old library Eski kütüphane - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - Bu kütüphane YACRKütüphanenin üst bir versiyonunda oluşturulmu. Yeni versiyonu indirmek ister misiniz ? - + Bu kütüphane YACReaderLibrary'nin daha yeni bir sürümüyle oluşturulmuş. Yeni sürüm şimdi indirilsin mi? - + Library '%1' is no longer available. Do you want to remove it? - Kütüphane '%1'ulaşılabilir değil. Kaldırmak ister misin? + '%1' kütüphanesi artık kullanılamıyor. Kaldırmak ister misiniz? - - Do you want remove - Kaldırmak ister misin + + Do you want to remove the library '%1'? + Şu kütüphaneyi kaldırmak ister misiniz: '%1'? - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - Kütüphane '%1 YACRKütüphanenin eski bir sürümünde oluşturulmuş, Kütüphaneyi yeniden oluşturmak ister misin? + '%1' kütüphanesi YACReaderLibrary'nin daha eski bir sürümüyle oluşturulmuş. Yeniden oluşturulması gerekiyor. Kütüphaneyi şimdi oluşturmak ister misiniz? - + Library not available - Kütüphane ulaşılabilir değil + Kütüphane kullanılamıyor - + YACReader Library YACReader Kütüphane - + Update needed Güncelleme gerekli - + Library name already exists - Kütüphane ismi zaten alınmış + Kütüphane adı zaten var - + There is another library with the name '%1'. - Bu başka bir kütüphanenin adı '%1'. + '%1' adında başka bir kütüphane zaten var. - + Download new version - Yeni versiyonu indir + Yeni sürümü indir - + Delete comics Çizgi romanları sil - + All the selected comics will be deleted from your disk. Are you sure? - Seçilen tüm çizgi romanlar diskten silinecek emin misin ? + Seçilen tüm çizgi romanlar diskinizden silinecek. Emin misiniz? - + Library not found Kütüphane bulunamadı - - library? - kütüphane? - - - + Are you sure? - Emin misin? + Emin misiniz? + Delete folder Klasörü sil - + Upgrade failed Yükseltme başarısız oldu - + There were errors during library upgrade in: Kütüphane yükseltmesi sırasında hatalar oluştu: - + Copying comics... Çizgi romanlar kopyalanıyor... - + Moving comics... Çizgi romanlar taşınıyor... @@ -1165,7 +1889,7 @@ - Please, select a folder first + Please select a folder first Lütfen, önce bir klasör seçiniz @@ -1181,92 +1905,121 @@ The selected folder and all its contents will be deleted from your disk. Are you sure? - Seçilen klasör ve tüm içeriği diskinizden silinecek. Emin misin? + Seçilen klasör ve tüm içeriği diskinizden silinecek. Emin misiniz? - + Unable to delete Silinemedi - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. Seçili klasörleri silmeye çalışırken bir sorun oluştu. Lütfen yazma izinlerini kontrol edin ve hiçbir uygulamanın bu klasörleri veya içerdikleri dosyaları kullanmadığından emin olun. - + + Edit smart list... + Akıllı listeyi düzenle... + + + + Delete smart list + Akıllı listeyi sil + + + + + + New folder... + Yeni klasör... + + + + + + Move to folder... + Klasöre taşı... + + + + Rename folder... + Klasörü yeniden adlandır... + + + + Match to library comics... + Kitaplıktaki çizgi romanlarla eşleştir... + + + + Track missing issues... + Eksik sayıları takip et... + + + + Export reading list... + Okuma listesini dışa aktar... + + + Rename or organize files Dosyaları yeniden adlandır veya düzenle - + Set the type of the selected comics Seçili çizgi romanların türünü ayarla - + + Save as smart list… + Akıllı liste olarak kaydet… + + + Search filters Arama filtreleri - + Unread Okunmamış - + In progress Devam eden - + Highly rated Yüksek puanlı - + Recently added Yakın zamanda eklenen - + Search syntax… Arama söz dizimi… - - A repair of this library is already running (%1). Wait for it to finish. - Bu kütüphanenin onarımı zaten çalışıyor (%1). Bitmesini bekleyin. - - - - The library is locked by a repair that did not finish. - Kütüphane, tamamlanmamış bir onarım tarafından kilitlendi. - - - - The library is locked by a repair started by %1. - Kütüphane, %1 tarafından başlatılan bir onarım tarafından kilitlendi. - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - Başka bir onarımın çalışmadığından eminseniz kilit kaldırılabilir. Kilit kaldırılıp devam edilsin mi? - - - + Package operation failed Paket işlemi başarısız oldu - + The covers package operation could not be completed. Kapak paketi işlemi tamamlanamadı. - + Restore recovery failed Geri yükleme kurtarması başarısız oldu @@ -1317,22 +2070,22 @@ Klasör: %1 Kütüphane veritabanı güncellenemedi ve klasörün diskteki yeni adı geri alınamadı. Kütüphanenin şimdi elle güncellenmesi gerekiyor. - + Save covers Kapakları kaydet - + You are adding too many libraries. Çok fazla kütüphane ekliyorsunuz. - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. Çok fazla kütüphane ekliyorsunuz. Muhtemelen üst düzey çizgi roman klasörünüzde yalnızca bir kütüphaneye ihtiyacınız vardır, sol kenar çubuğundaki klasörler bölümünü kullanarak herhangi bir alt klasöre göz atabilirsiniz. @@ -1340,48 +2093,42 @@ Muhtemelen üst düzey çizgi roman klasörünüzde yalnızca bir kütüphaneye YACReaderLibrary daha fazla kütüphane oluşturmanıza engel olmaz ancak kütüphane sayısını düşük tutmalısınız. - - + + YACReader library database (*.ydb) - YACReader kitaplık veritabanı (*.ydb) + YACReader kütüphane veritabanı (*.ydb) - + The library database backup was created at: %1 - Kitaplık veritabanı yedeği şu konumda oluşturuldu: + Kütüphane veritabanı yedeği şu konumda oluşturuldu: %1 - + Unable to create the library database backup: %1 - Kitaplık veritabanı yedeği oluşturulamadı: + Kütüphane veritabanı yedeği oluşturulamadı: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? - Geri yüklemeden önce YACReaderLibraryServer'ı ve bu kitaplığı kullanan diğer tüm YACReader uygulamalarını kapatın. Devam edilsin mi? + Geri yüklemeden önce YACReaderLibraryServer'ı ve bu kütüphaneyi kullanan diğer tüm YACReader uygulamalarını kapatın. Devam edilsin mi? - + Restoring library database... - Kitaplık veritabanı geri yükleniyor... + Kütüphane veritabanı geri yükleniyor... - + The current library database is invalid. Restore the selected backup anyway? - Geçerli kitaplık veritabanı geçersiz. Seçilen yedek yine de geri yüklensin mi? - - - - - The library maintenance lock may be stale. Remove it and retry? - Kitaplık bakım kilidi eski kalmış olabilir. Kaldırıp yeniden denensin mi? + Geçerli kütüphane veritabanı geçersiz. Seçilen yedek yine de geri yüklensin mi? - + Restart YACReaderLibrary before attempting recovery again. @@ -1390,82 +2137,88 @@ Restart YACReaderLibrary before attempting recovery again. Kurtarmayı yeniden denemeden önce YACReaderLibrary'yi yeniden başlatın. - + The library database was restored successfully. Update the library now? - Kitaplık veritabanı başarıyla geri yüklendi. Kitaplık şimdi güncellensin mi? + Kütüphane veritabanı başarıyla geri yüklendi. Kütüphane şimdi güncellensin mi? - + Library database damaged - Kitaplık veritabanı hasarlı + Kütüphane veritabanı hasarlı - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. - '%1' kitaplığının veritabanı hasarlı olduğundan normal güncellemeler, bakım ve yedeklemeler kullanılamıyor. YACReader veritabanını onarmayı deneyebilir. Bazı hasarlı veriler kurtarılamayabilir. Mevcut yedekler değiştirilmeyecektir. + '%1' kütüphanesinin veritabanı hasarlı olduğundan normal güncellemeler, bakım ve yedeklemeler kullanılamıyor. YACReader veritabanını onarmayı deneyebilir. Bazı hasarlı veriler kurtarılamayabilir. Mevcut yedekler değiştirilmeyecektir. - + Attempt repair Onarmayı dene - + Restore a backup... Bir yedeği geri yükle... - + Repairing library database... - Kitaplık veritabanı onarılıyor... + Kütüphane veritabanı onarılıyor... - - + + + Library database repair - Kitaplık veritabanını onar + Kütüphane veritabanını onar + + + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + '%1' kitaplığının veritabanı kontrol edilsin ve gerekirse onarılmaya çalışılsın mı? Mevcut yedekler değiştirilmeyecek. - - Another maintenance operation is currently using this library. Try again after it finishes. - Başka bir bakım işlemi şu anda bu kitaplığı kullanıyor. İşlem bittikten sonra yeniden deneyin. + + Check and repair + Kontrol et ve onar - + The library database is already valid. - Kitaplık veritabanı zaten geçerli. + Kütüphane veritabanı zaten geçerli. - + Library database repaired - Kitaplık veritabanı onarıldı + Kütüphane veritabanı onarıldı - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 - Kitaplık veritabanı dizinleri yeniden oluşturularak onarıldı. Hasarlı özgün dosya şu konumda korundu: + Kütüphane veritabanı dizinleri yeniden oluşturularak onarıldı. Hasarlı özgün dosya şu konumda korundu: %1 - + Library database rebuilt - Kitaplık veritabanı yeniden oluşturuldu + Kütüphane veritabanı yeniden oluşturuldu - + The library database was rebuilt successfully. The damaged original was preserved at: %1 Update the library now? - Kitaplık veritabanı başarıyla yeniden oluşturuldu. Hasarlı özgün dosya şu konumda korundu: + Kütüphane veritabanı başarıyla yeniden oluşturuldu. Hasarlı özgün dosya şu konumda korundu: %1 -Kitaplık şimdi güncellensin mi? +Kütüphane şimdi güncellensin mi? - + The damaged original was preserved at: @@ -1476,82 +2229,122 @@ Hasarlı özgün dosya şu konumda korundu: %1 - + Library database repair failed - Kitaplık veritabanı onarılamadı + Kütüphane veritabanı onarılamadı - + The library database could not be repaired: %1%2 You can restore a backup from the Library menu or recreate the library. - Kitaplık veritabanı onarılamadı: + Kütüphane veritabanı onarılamadı: %1%2 -Kitaplık menüsünden bir yedeği geri yükleyebilir veya kitaplığı yeniden oluşturabilirsiniz. +Kütüphane menüsünden bir yedeği geri yükleyebilir veya kütüphaneyi yeniden oluşturabilirsiniz. - + Remove and delete metadata and backups Meta verileri ve yedekleri kaldır ve sil - + Library info Kütüphane bilgisi - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. - Seçilen çizgi romanlar silinmeye çalışılırken bir sorun oluştu. Lütfen seçilen dosyalarda veya klasörleri içeren yazma izinlerini kontrol edin. + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + Seçilen çizgi romanlar silinmeye çalışılırken bir sorun oluştu. Lütfen seçilen dosyalarda veya bunları içeren klasörde yazma izinlerini kontrol edin. - + Assign comics numbers Çizgi roman numaraları ata - - Assign numbers starting in: - Şunlardan başlayarak numaralar ata: + + Assign numbers starting at: + Şu numaradan başlayarak numaralar ata: - + Invalid image Geçersiz resim - + The selected file is not a valid image. Seçilen dosya geçerli bir resim değil. - + + Error saving cover Kapak kaydedilirken hata oluştu - + + There was an error saving the cover image. Kapak resmi kaydedilirken bir hata oluştu. - + + + Match to library comic + Kitaplıktaki çizgi romanla eşleştir + + + + This also gives the comic to entries in %n other list(s): %1 + + Bu işlem %n başka listedeki kayıtlara da bu çizgi romanı atar: %1 + + + + + The comic could not be matched. Nothing was changed. + Çizgi roman eşleştirilemedi. Hiçbir şey değiştirilmedi. + + + + The selected missing comics will be removed from this folder. Are you sure? + Seçili eksik çizgi romanlar bu klasörden kaldırılacak. Emin misiniz? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + Seçili çizgi romanlar diskinizden silinecek ve seçili eksik çizgi romanlar bu klasörden kaldırılacak. Emin misiniz? + + + + Remove the comics from Reading? Their reading progress will be reset. + Çizgi romanlar Okunuyor bölümünden kaldırılsın mı? Okuma ilerlemeleri sıfırlanacak. + + + + The comics will be removed from Favorites. Are you sure? + Çizgi romanlar Favorilerden kaldırılacak. Emin misiniz? + + + Remove comics Çizgi romanları kaldır - + Comics will only be deleted from the current label/list. Are you sure? - Çizgi romanlar yalnızca mevcut etiketten/listeden silinecektir. Emin misin? + Çizgi romanlar yalnızca mevcut etiketten/listeden silinecektir. Emin misiniz? - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1570,39 +2363,39 @@ Eksik dosyalar: %3 Open an existing library - Çıkış kütüphanesini aç + Mevcut bir kütüphaneyi aç Export comics info - Çizgi roman bilgilerini göster + Çizgi roman bilgilerini dışa aktar Import comics info - Çizgi roman bilgilerini çıkart + Çizgi roman bilgilerini içe aktar Pack covers - Paket kapakları + Kapakları paketle Pack the covers of the selected library - Kütüphanede ki kapakları paketle + Seçili kütüphanedeki kapakları paketle Unpack covers - Kapakları aç + Kapakları paketten çıkar Unpack a catalog - Kataloğu çkart + Kataloğu paketten çıkar @@ -1612,457 +2405,528 @@ Eksik dosyalar: %3 Update current library - Kütüphaneyi güncelle + Geçerli kütüphaneyi güncelle Back up library database - Kitaplık veritabanını yedekle + Kütüphane veritabanını yedekle Create a backup of the current library database - Geçerli kitaplık veritabanının yedeğini oluştur + Geçerli kütüphane veritabanının yedeğini oluştur Restore library database backup - Kitaplık veritabanı yedeğini geri yükle + Kütüphane veritabanı yedeğini geri yükle Restore the current library database from a backup - Geçerli kitaplık veritabanını bir yedekten geri yükle + Geçerli kütüphane veritabanını bir yedekten geri yükle + Repair library database... + Kitaplık veritabanını onar... + + + + Check the current library database and attempt to repair any damage + Geçerli kitaplık veritabanını kontrol et ve hasarı onarmaya çalış + + + + Recover library access... + Kitaplık erişimini kurtar... + + + + Recover access after an interrupted library maintenance operation + Kesintiye uğrayan kitaplık bakımından sonra erişimi kurtar + + + Repair covers and comic info Kapakları ve çizgi roman bilgilerini onar - + Retry comics with missing covers or incomplete information Kapağı eksik veya bilgileri tamamlanmamış çizgi romanları yeniden işle - + Rename library Kütüphaneyi yeniden adlandır - + Rename current library - Kütüphaneyi adlandır + Geçerli kütüphaneyi yeniden adlandır - + Remove library - Kütüphaneyi sil + Kütüphaneyi kaldır - + Remove current library from your collection - Kütüphaneyi koleksiyonundan kaldır + Geçerli kütüphaneyi koleksiyonunuzdan kaldır - + Rescan library for XML info - XML bilgisi için kitaplığı yeniden tarayın + Kütüphaneyi XML bilgisi için yeniden tara - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. - Komik dosyalara gömülü XML bilgilerini bulmaya çalışır. Bunu yalnızca kitaplık 9.8.2 veya önceki sürümlerle oluşturulmuşsa veya XML bilgilerini dosyalara eklemek için üçüncü taraf yazılım kullanıyorsanız yapmanız gerekir. + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. + Çizgi roman dosyalarına gömülü XML bilgilerini bulmaya çalışır. Bunu yalnızca kütüphane 9.8.2 veya önceki sürümlerle oluşturulmuşsa ya da XML bilgilerini dosyalara eklemek için üçüncü taraf yazılım kullanıyorsanız yapmanız gerekir. - + Open library folder... Kütüphane klasörünü aç... - + Open the root folder of the current library Geçerli kütüphanenin kök klasörünü aç - + Show library info - Kitaplık bilgilerini göster + Kütüphane bilgilerini göster - + Show information about the current library - Geçerli kitaplık hakkındaki bilgileri göster + Geçerli kütüphane hakkındaki bilgileri göster - + Open current comic Seçili çizgi romanı aç - - Open current comic on YACReader - YACReader'ı geçerli çizgi roman okuyucsu seç + + Open current comic in YACReader + Geçerli çizgi romanı YACReader'da aç - + Save selected covers to... Seçilen kapakları şuraya kaydet... - + Save covers of the selected comics as JPG files Seçilen çizgi romanların kapaklarını JPG dosyaları olarak kaydet - - + + Set as read Okundu olarak işaretle - + Set comic as read Çizgi romanı okundu olarak işaretle - - + + Set as unread - Hepsini okunmadı işaretle + Okunmadı olarak işaretle - + Set comic as unread - Çizgi Romanı okunmadı olarak seç + Çizgi romanı okunmadı olarak işaretle - - + + manga - manga t?r? + manga - + Set issue as manga Sayıyı manga olarak ayarla - - + + comic - komik + çizgi roman - + Set issue as normal Sayıyı normal olarak ayarla - + western manga batı mangası - + Set issue as western manga - Konuyu western mangası olarak ayarla + Sayıyı batı mangası olarak ayarla - - + + web comic web çizgi romanı - + Set issue as web comic - Sorunu web çizgi romanı olarak ayarla + Sayıyı web çizgi romanı olarak ayarla - - + + yonkoma - d?rt panelli + yonkoma - + Set issue as yonkoma - Sorunu yonkoma olarak ayarla + Sayıyı yonkoma olarak ayarla - + Show/Hide marks - Altçizgileri aç/kapa + İşaretleri göster/gizle - + Show or hide read marks - Okundu işaretlerini göster yada gizle + Okundu işaretlerini göster veya gizle - + Show/Hide recent indicator - Son göstergeyi Göster/Gizle + Son eklenenler göstergesini göster/gizle - + Show or hide recent indicator - Son göstergeyi göster veya gizle + Son eklenenler göstergesini göster veya gizle - - + + Fullscreen mode on/off Tam ekran modu açık/kapalı - + Help, About YACReader - Yardım, Bigli, YACReader + Yardım, YACReader Hakkında - + Add new folder Yeni klasör ekle - + Add new folder to the current library - Geçerli kitaplığa yeni klasör ekle + Geçerli kütüphaneye yeni klasör ekle - + Rename folder Klasörü yeniden adlandır - + Rename the current folder on disk and in the library Geçerli klasörü diskte ve kütüphanede yeniden adlandır - + Delete folder Klasörü sil - + Delete current folder from disk Geçerli klasörü diskten sil - + Select root node - Kökü seçin + Kök düğümü seç - + Expand all nodes - Tüm düğümleri büyüt + Tüm düğümleri genişlet - + Collapse all nodes - Tüm düğümleri kapat + Tüm düğümleri daralt - + Show options dialog Ayarları göster - + Show comics server options dialog - Çizgi romanların server ayarlarını göster + Çizgi roman sunucusu ayarlarını göster - - + + Change between comics views Çizgi roman görünümleri arasında değiştir - + + Grid view + Izgara görünümü + + + + Flow view + + + + + Info view + + + + Open folder... - Dosyayı aç... + Klasörü aç... - - + + Rename files... Organize files Dosyaları yeniden adlandır... - - + + Organize into folders... Klasörlere düzenle... - + Set as uncompleted Tamamlanmamış olarak ayarla - + Set as completed Tamamlanmış olarak ayarla - + Set custom cover Özel kapak ayarla - + Delete custom cover Özel kapağı sil - + western manga (left to right) Batı mangası (soldan sağa) - + Open containing folder... - Klasör açılıyor... + Bulunduğu klasörü aç... - Reset comic rating - Çizgi roman reytingini sıfırla + + New reading list + Yeni okuma listesi - + Select all comics Tüm çizgi romanları seç - + Edit Düzenle - + Assign current order to comics Geçerli sırayı çizgi romanlara ata - + Update cover Kapağı güncelle - + Delete selected comics Seçili çizgi romanları sil - + Delete metadata from selected comics Seçilen çizgi romanlardan meta verileri sil - + Download tags from Comic Vine Etiketleri Comic Vine sitesinden indir - + Focus search line Arama satırına odaklan - + Focus comics view - Çizgi roman görünümüne odaklanın + Çizgi roman görünümüne odaklan - + Edit shortcuts Kısayolları düzenle - + &Quit &Çıkış - + Update folder Klasörü güncelle - + Update current folder Geçerli klasörü güncelle - + Scan legacy XML metadata - Eski XML meta verilerini tarayın + Eski XML meta verilerini tara - - Add new reading list - Yeni okuma listesi ekle + + Add a new reading list to the current library + Geçerli kütüphaneye yeni bir okuma listesi ekle - - Add a new reading list to the current library - Geçerli kitaplığa yeni bir okuma listesi ekle + + New smart list... + Yeni akıllı liste... + + + + Add a saved search to the current library + Geçerli kitaplığa kayıtlı bir arama ekle + + + + New folder... + Yeni klasör... + + + + Add a folder for reading lists or smart lists + Okuma listeleri veya akıllı listeler için klasör ekle + + + + Import reading lists... + Okuma listelerini içe aktar... + + + + Import CBL reading lists into the current library + CBL okuma listelerini geçerli kitaplığa içe aktar + + + + New + Yeni + + + + Add or import reading lists + Okuma listesi ekle veya içe aktar - + Remove reading list Okuma listesini kaldır - + Remove current reading list from the library Geçerli okuma listesini kütüphaneden kaldır - + Add new label Yeni etiket ekle - + Add a new label to this library - Bu kitaplığa yeni bir etiket ekle + Bu kütüphaneye yeni bir etiket ekle - + Rename selected list Seçilen listeyi yeniden adlandır - + Rename any selected labels or lists Seçilen etiketleri ya da listeleri yeniden adlandır - + Add to... Şuraya ekle... - + Favorites Favoriler - + Add selected comics to favorites list Seçilen çizgi romanları favoriler listesine ekle - - + + Reset rating Puanı sıfırla @@ -2072,12 +2936,12 @@ Eksik dosyalar: %3 comic - komik + çizgi roman manga - manga t?r? + manga @@ -2091,91 +2955,104 @@ Eksik dosyalar: %3 - 4koma (top to botom) + 4koma (top to bottom) 4koma (yukarıdan aşağıya) - - - + + + Set type Türü ayarla - + Library Kütüphane - + Folder Klasör - + Comic Çizgi roman - + + Match to library comic... + Kitaplıktaki çizgi romanla eşleştir... + + + + + + + Track missing issues... + Eksik sayıları takip et... + + + Open folder... - Dosyayı aç... + Klasörü aç... - + Update folder Klasörü güncelle - + Rename folder Klasörü yeniden adlandır - + Rename files... Dosyaları yeniden adlandır... - + Organize into folders... Klasörlere düzenle... - + Rescan library for XML info - XML bilgisi için kitaplığı yeniden tarayın + Kütüphaneyi XML bilgisi için yeniden tara - + Set as uncompleted Tamamlanmamış olarak ayarla - + Set as completed Tamamlanmış olarak ayarla - + Set as read Okundu olarak işaretle - - + + Set as unread - Hepsini okunmadı işaretle + Okunmadı olarak işaretle - + Set custom cover Özel kapak ayarla - - + + Delete custom cover Özel kapağı sil @@ -2216,27 +3093,73 @@ Eksik dosyalar: %3 LocalComicListModel - + file name dosya adı + + MoveToFolderDialog + + + Move to folder + Klasöre taşı + + + + Top level + En üst düzey + + + + Move "%1" to: + "%1" şuraya taşınsın: + + + + NewListFolderDialog + + + New folder + Yeni klasör + + + + Reading lists + Okuma listeleri + + + + Smart lists + Akıllı listeler + + + + Folder name: + Klasör adı: + + + + For: + İçin: + + NoLibrariesWidget create your first library - İlk kütüphaneni oluştur + İlk kütüphanenizi oluşturun You don't have any libraries yet - Henüz bir kütüphaneye sahip değilsin + Henüz bir kütüphaneniz yok - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>Yeni bir kütüphane oluşturabilmeniçin kütüphane</p><p>No olvides que puedes usar YACReader como una aplicación independiente para leer los cómics en tu ordenador.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>Herhangi bir klasörde kütüphane oluşturabilirsiniz; YACReaderLibrary bu klasördeki tüm çizgi romanları ve klasörleri içe aktaracaktır. Daha önce bir kütüphane oluşturduysanız onları açabilirsiniz.</p><p>YACReader'ı bilgisayarınızdaki çizgi romanları okumak için bağımsız bir uygulama olarak da kullanabileceğinizi unutmayın.</p> @@ -2287,11 +3210,11 @@ Eksik dosyalar: %3 Close to tray - Tepsiyi kapat + Sistem tepsisine kapat - Start into the system tray + Start in the system tray Sistem tepsisinde başlat @@ -2318,7 +3241,7 @@ Eksik dosyalar: %3 Consider 'recent' items added or updated since X days ago - X gün öncesinden bu yana eklenen veya güncellenen 'en son' öğeleri göz önünde bulundurun + Son X gün içinde eklenen veya güncellenen öğeleri 'yeni' olarak kabul edin @@ -2338,7 +3261,7 @@ Eksik dosyalar: %3 Update libraries at startup - Başlangıçta kitaplıkları güncelleyin + Başlangıçta kütüphaneleri güncelle @@ -2348,7 +3271,7 @@ Eksik dosyalar: %3 Update libraries periodically - Kitaplıkları düzenli aralıklarla güncelleyin + Kütüphaneleri düzenli aralıklarla güncelleyin @@ -2393,26 +3316,26 @@ Eksik dosyalar: %3 Update libraries at certain time - Kitaplıkları belirli bir zamanda güncelle + Kütüphaneleri belirli bir zamanda güncelle Time: - Zaman: + Saat: - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. UYARI! Kütüphane güncellemeleri sırasında veritabanına yazma işlemi devre dışı bırakılır! Uygulamayı aktif olarak kullanırken güncelleme planlamayın. Otomatik güncellemeler sırasında uygulama, güncelleme bitene kadar bazı eylemleri engelleyecektir. -Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki yükleme göstergesine dokunun. +Otomatik güncellemeyi durdurmak için Kütüphaneler başlığının yanındaki yükleme göstergesine dokunun. @@ -2422,7 +3345,7 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Compare the modified date of files when updating a library (not recommended) - Kitaplığı güncellerken dosyaların değiştirilme tarihini karşılaştırın (önerilmez) + Kütüphaneyi güncellerken dosyaların değiştirilme tarihini karşılaştırın (önerilmez) @@ -2432,7 +3355,7 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Opacity level - Matlık düzeyi + Opaklık düzeyi @@ -2442,11 +3365,11 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Use selected comic cover as background - Seçilen çizgi roman kapanığı arka plan olarak kullan + Seçilen çizgi romanın kapağını arka plan olarak kullan - Restore defautls + Restore defaults Varsayılanları geri yükle @@ -2457,7 +3380,7 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Display continue reading banner - Okuma devam et bannerını göster + Okumaya devam et banner'ını göster @@ -2514,76 +3437,177 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y OrganizeFiles - + + + Unknown Series + Bilinmeyen Seri + + + + series + seri + + + + Unknown Publisher + Bilinmeyen Yayıncı + + + + publisher + yayıncı + + + + title + başlık + + + + Unknown Comic + Bilinmeyen Çizgi Roman + + + + Series #Number - Title + Seri #Sayı - Başlık + + + + Series #Number + Seri #Sayı + + + + Series #Number (of Count) + Seri #Sayı (Toplam içinde) + + + + Number - Title + Sayı - Başlık + + + + Series #Number (Year) + Seri #Sayı (Yıl) + + + + Publisher / Series / Series #Number - Title + Yayıncı / Seri / Seri #Sayı - Başlık + + + + Publisher / Imprint / Series / Series #Number - Title + Yayıncı / Yayın markası / Seri / Seri #Sayı - Başlık + + + + Series / Series #Number + Seri / Seri #Sayı + + + + Publisher / Series / Series #Number (Year) + Yayıncı / Seri / Seri #Sayı (Yıl) + + + + Series / original file name + Seri / özgün dosya adı + + + Renamed, %1 is already in use Yeniden adlandırıldı, %1 zaten kullanımda - + Missing metadata: %1 - Eksik üstveri: %1 + Eksik meta veri: %1 %1 could not be created %1 oluşturulamadı + + + %1 (copy also failed: %2) + %1 (kopyalama da başarısız oldu: %2) + + + + the copy did not have the same size as the original + kopyanın boyutu özgün dosyayla aynı değildi + + + + the original could not be deleted after it was copied + özgün dosya kopyalandıktan sonra silinemedi + + + + The destination folder could not be created. + Hedef klasör oluşturulamadı. + + + + %1 (the file was left at %2) + %1 (dosya %2 konumunda bırakıldı) + OrganizeFilesCoordinator - - + + Organize files Dosyaları düzenle - + This folder does not contain any comics. Bu klasör hiç çizgi roman içermiyor. - - This library is busy: %1 - Bu kütüphane meşgul: %1 - - - + the library database could not be opened kütüphane veritabanı açılamadı - + the library database could not be locked for writing kütüphane veritabanı yazma için kilitlenemedi - + a folder entry could not be restored bir klasör kaydı geri yüklenemedi - + a comic entry could not be updated bir çizgi roman kaydı güncellenemedi - + the library database could not be saved: %1 kütüphane veritabanı kaydedilemedi: %1 - + the record of the last organize run could not be read son düzenleme işleminin kaydı okunamadı - + the folder %1 could not be created %1 klasörü oluşturulamadı - + %n file(s) could not be moved back %n dosya geri taşınamadı @@ -2592,10 +3616,6 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y OrganizeFilesDialog - - Format: - Formato: - Organize files @@ -2727,19 +3747,11 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Cancel Vazgeç - - Copy the list - Listeyi kopyala - Undo Geri al - - Close - Kapat - Copy failure details @@ -3143,274 +4155,383 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y PropertiesDialog - + Day: Gün: - + Plot - Argumento + Konu - + Size: Boyut: - + Year: Yıl: - + Inker(s): - Mürekkep(ler): + Mürekkepleyen(ler): - + Publishing Yayın - + Publisher: Yayıncı: - + General info Genel bilgi - + Color/BW: - Renk/BW: + Renk/S-B: - + Edit selected comics information Seçilen çizgi roman bilgilerini düzenle - + Penciller(s): Çizenler: - + Colorist(s): Renklendiren: - + + Issue number: - Yayın numarası: + Sayı numarası: - + Month: Ay: - + Notes: Notlar: - + Synopsis: Özet: - + Title: Başlık: - + Not found - Bulunamad + Bulunamadı - + Characters: Karakterler: - + Authors Yazarlar - + Age rating: Yaş sınırı: - + Story arc: - Hiakye: + Hikâye yayı: - + Notes Notlar - + Load previous page as cover Önceki sayfayı kapak olarak yükle - + Load next page as cover Sonraki sayfayı kapak olarak yükle - + Reset cover to the default image Kapağı varsayılan görüntüye sıfırla - + Load custom cover image Özel kapak resmini yükle - + Series: - Seriler: + Seri: + + + + e.g. 1-5, 8, 0.5, Annual 1 + ör. 1-5, 8, 0.5, Annual 1 - + alt. number: alternatif sayı: - + Alternate series: Alternatif seri: - + Series Group: Seri Grubu: - + Writer(s): Yazarlar: - + Editor(s): Editör(ler): - + Imprint: - Künye: + Yayın markası: - + Type: Tip: - + Language (ISO): Dil (ISO): - + Teams: Takımlar: - + Locations: Konumlar: - + Main character or team: Ana karakter veya takım: - + Review: - Gözden geçirmek: + İnceleme: - + Tags: Etiketler: - + Comic not found. You should update your library. - Çizgi roman bulunamadı. Kütüphaneyi güncellemelisin. + Çizgi roman bulunamadı. Kütüphanenizi güncellemelisiniz. - + Edit comic information Çizgi roman bilgisini düzenle - + + + Track missing issues in "%1" + "%1" içindeki eksik sayıları takip et + + + + Issues: + Sayılar: + + + + Track + Takip et + + + + At most %1 issues can be tracked at once. + Tek seferde en fazla %1 sayı takip edilebilir. + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + Sayıları kontrol edin: aralıklar artan sırada olmalı ve virgüller arasındaki kısımlar boş olmamalıdır. + + + + No new issues to track. + Takip edilecek yeni sayı yok. + + + + %n issue(s) will be tracked: %1. + + %n sayı takip edilecek: %1. + + + + + That is a lot of issues; check the numbers. + Çok fazla sayı var; numaraları kontrol edin. + + + + %n issue(s) already in the folder. + + %n sayı zaten klasörde. + + + + + %n repeated issue(s). + + %n yinelenen sayı. + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + Takip edilmeyen sayılar + + + + The missing issues could not be tracked. + Eksik sayılar takip edilemedi. + + + + Comic skipped + Çizgi roman atlandı + + + + A comic was removed from the library or now has a different file, so it is skipped. + Bir çizgi roman kitaplıktan kaldırıldı veya artık farklı bir dosyası var, bu nedenle atlanıyor. + + + + + Changes not saved + Değişiklikler kaydedilmedi + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + Düzenlediğiniz bir çizgi roman kitaplıktan kaldırıldı veya artık farklı bir dosyası var. Değişiklikleriniz kaydedilmedi. + + + + The changes could not be saved. + Değişiklikler kaydedilemedi. + + + Invalid cover Geçersiz kapak - + The image is invalid. Resim geçersiz. - + Cover page Kapak sayfası - + Cover Artist(s): - Kapak artisti: + Kapak çizeri: - + Volume: Cilt: - + Format: - Formato: + Biçim: - + Letterer(s): - Mesaj(lar): + Harflendiren(ler): - - + + of: - ile ilgili: + toplam: - + Arc number: - Ark numarası: + Hikâye yayı numarası: - + Genre: Tür: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> Comic Vine bağlantısı: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> görüntüle </a> @@ -3420,20 +4541,20 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y 7z lib not found - 7z lib bulunamadı + 7z kütüphanesi bulunamadı unable to load 7z lib from ./utils - ./utils'den 7z lib yüklenemiyor + ./utils içinden 7z kütüphanesi yüklenemedi - + Select custom cover Özel kapak seçin - + Images (%1) Resimler (%1) @@ -3455,7 +4576,7 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Folders - Klasör + Klasörler @@ -3466,38 +4587,171 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y ReadingListManagementCoordinator - + Add new reading lists Yeni okuma listeleri ekle - - + + List name: Liste adı: - + + Import reading lists + Okuma listelerini içe aktar + + + + Reading lists (*.cbl *.json);;All files (*) + Okuma listeleri (*.cbl *.json);;Tüm dosyalar (*) + + + + New smart list + Yeni akıllı liste + + + + + The smart list could not be saved. + Akıllı liste kaydedilemedi. + + + + Edit smart list + Akıllı listeyi düzenle + + + + Delete folder + Klasörü sil + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + "%1" klasörü ve içindeki her şey silinsin mi? İçindeki klasörler ve listeler silinir; çizgi roman dosyalarınız korunur. + + + Delete list/label Listeyi/Etiketi sil - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - Seçilen öğe silinecek, çizgi romanlarınız veya klasörleriniz diskinizden SİLİNMEYECEKTİR. Emin misin? + Seçilen öğe silinecek, çizgi romanlarınız veya klasörleriniz diskinizden SİLİNMEYECEKTİR. Emin misiniz? - + Rename list name Listeyi yeniden adlandır + + + Rename folder + Klasörü yeniden adlandır + + + + + Folder name: + Klasör adı: + + + + + + New folder + Yeni klasör + + + + + The folder could not be created. + Klasör oluşturulamadı. + + + + Reading list + Okuma listesi + + + + CBL reading list (*.cbl) + CBL okuma listesi (*.cbl) + + + + JSON reading list (*.json) + JSON okuma listesi (*.json) + + + + Web page (*.html) + Web sayfası (*.html) + + + + + Export reading list + Okuma listesini dışa aktar + + + + Exporting reading list... + Okuma listesi dışa aktarılıyor... + + + + The file could not be written. + Dosya yazılamadı. + + + + Move to folder + Klasöre taşı + + + + "%1" could not be moved. + "%1" taşınamadı. + + + + ReadingListPage + + + %n comic(s) + + %n çizgi roman + + + + + %n missing + + %n eksik + + + + + Missing + Eksik + + + + Exported from YACReader on %1 + %1 tarihinde YACReader'dan dışa aktarıldı + RenameLibraryDialog Rename current library - Kütüphaneyi yeniden adlandır + Geçerli kütüphaneyi yeniden adlandır @@ -3511,16 +4765,16 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y - New Library Name : - Yeni Kütüphane Adı : + New Library Name: + Yeni Kütüphane Adı: ScraperResultsPaginator - Number of volumes found : %1 - Bulunan bölüm sayısı: %1 + Number of volumes found: %1 + Bulunan cilt sayısı: %1 @@ -3530,8 +4784,8 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y - Number of %1 found : %2 - Sayı %1, bulunan : %2 + Number of %1 found: %2 + Bulunan %1 sayısı: %2 @@ -3818,7 +5072,7 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Series: - Seriler: + Seri: @@ -3893,6 +5147,11 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Just start typing. Plain text search across all metadata. Yazmaya başlamanız yeterlidir. Düz metin tüm üst verilerde aranır. + + + %1 -> search for `%1` in any database field + %1 -> herhangi bir veritaban? alan?nda `%1` ara + 1. Search everywhere @@ -3988,7 +5247,9 @@ Otomatik güncellemeyi durdurmak için Kitaplıklar başlığının yanındaki y Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - İpuçları: Boşluklar AND gibi davranır ve aramalar büyük/küçük harfe duyarlı değildir. Bir değerde boşluk kullanmak için tırnak işaretlerini kullanın. + İpuçları: +Boşluklar AND gibi davranır ve aramalar büyük/küçük harfe duyarlı değildir. +Bir değerde boşluk kullanmak için tırnak işaretlerini kullanın. @@ -4131,7 +5392,7 @@ Use quotes to include spaces in a value. Series: - Seriler: + Seri: @@ -4143,13 +5404,13 @@ Use quotes to include spaces in a value. SelectComic - Please, select the right comic info. + Please select the right comic info. Lütfen, doğru çizgi roman bilgisini seçin. comics - çizgi roman + çizgi romanlar @@ -4171,7 +5432,7 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. Çizgi romanınız için doğru seriyi seçin. @@ -4182,7 +5443,7 @@ Use quotes to include spaces in a value. volumes - sayı + ciltler @@ -4210,54 +5471,59 @@ Use quotes to include spaces in a value. Unknown - Bilinmiyor + Bilinmiyor %1 (%2 unknown) %1 (%n unknown) - + %1 (%2 bilinmiyor) %1 series - + %1 seri - + %1 comics selected %n comic(s) selected - %1 çizgi roman seçildi + %1 çizgi roman seçildi - + Read - Oku + Okundu - + In progress - Devam eden + Devam eden - + Unread - Okunmamış + Okunmamış - + Total pages - + Toplam sayfa - + Total size - + Toplam boyut + + + + Missing + Eksik - + Series - Seri + Seri @@ -4274,8 +5540,8 @@ Use quotes to include spaces in a value. - You are trying to get information for various comics at once, are they part of the same series? - Aynı anda çeşitli çizgi romanlar için bilgi almaya çalışıyorsunuz, bunlar aynı serinin parçası mı? + You are trying to get information for various comics at once. Are they part of the same series? + Aynı anda çeşitli çizgi romanlar için bilgi almaya çalışıyorsunuz. Bunlar aynı serinin parçası mı? @@ -4294,7 +5560,7 @@ Use quotes to include spaces in a value. Devices on this network can reach your library at the address below. - Bu ağdaki cihazlar aşağıdaki adresten kitaplığınıza erişebilir. + Bu ağdaki cihazlar aşağıdaki adresten kütüphanenize erişebilir. @@ -4304,7 +5570,7 @@ Use quotes to include spaces in a value. Port - Liman + Port @@ -4331,59 +5597,74 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader, iOS ve Android için kullanılabilir. <a href='https://ios.yacreader.com'>iOS</a> veya <a href='https://android.yacreader.com'>Android</a> sürümünü keşfedin. - - enable the server - erişilebilir server - Set port set port Portu ayarla + + + SmartListDialog + + + New smart list + Yeni akıllı liste + - Server connectivity information - Sunucu bağlantı bilgileri + + Edit smart list + Akıllı listeyi düzenle - Scan it! - Tara! + + Search filters and syntax + Arama filtreleri ve sözdizimi - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader, iOS ve Android cihazlarda kullanılabilir.<br/>Bunu <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> veya <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a> için keşfedin. + + Name: + Ad: - Choose an IP address - IP adresi seçin + + Search: + Arama: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. Lütfen, çizgi romanların bilgileriyle eşleşene kadar soldaki çizgi roman listesini sıralayın. - + sort comics to match comic information çizgi roman bilgilerini eşleştirmek için çizgi romanları sıralayın - + issues sayı - + remove selected comics seçilen çizgi romanları kaldır - + restore all removed comics - tüm seçilen çizgi romanları geri yükle + kaldırılan tüm çizgi romanları geri yükle + + + + Add the %n other issue(s) of this volume as missing comics + + Bu cildin diğer %n sayısını eksik çizgi roman olarak ekle + @@ -4406,7 +5687,7 @@ Use quotes to include spaces in a value. i - Ben + i @@ -4421,32 +5702,32 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - Kullanıcı arayüzünde seçilen değeri (macenta / geçişli / 0↔10) yanıp sönmek için basılı tutun. Sürümler orijinali geri yükler. + Kullanıcı arayüzünde seçilen değeri (macenta / geçişli / 0↔10) yanıp söndürmek için basılı tutun. Bırakıldığında orijinal değer geri yüklenir. Search… - Aramak… + Ara… Light - Işık + Açık Dark - Karanlık + Koyu ID: - İD: + ID: Display name: - Ekran adı: + Görünen ad: @@ -4501,7 +5782,7 @@ Use quotes to include spaces in a value. true - doğru + true @@ -4509,7 +5790,7 @@ Use quotes to include spaces in a value. false - YANLIŞ + false @@ -4605,10 +5886,18 @@ Use quotes to include spaces in a value. - Updating.... + Updating... Güncelleniyor... + + ViewSelector + + + View + + + VolumeComicsModel @@ -4640,7 +5929,7 @@ Use quotes to include spaces in a value. Presets: - Hazırlayan: + Ön ayarlar: @@ -4670,12 +5959,12 @@ Use quotes to include spaces in a value. Show advanced settings - Daha fazla ayar göster + Gelişmiş ayarları göster Custom: - Kişisel: + Özel: @@ -4695,22 +5984,22 @@ Use quotes to include spaces in a value. Central gap - Boş merkez + Merkez boşluğu Zoom - Yakınlaş + Zoom Y offset - Y dengesi + Y ofseti Z offset - Z dengesi + Z ofseti @@ -4720,7 +6009,7 @@ Use quotes to include spaces in a value. Visibility - Görünülebilirlik + Görünürlük @@ -4744,8 +6033,8 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - VSync kullan + Use VSync (improves image quality in fullscreen mode, but reduces performance) + VSync kullan (tam ekran modunda görüntü kalitesini artırır, performansı düşürür) @@ -4768,7 +6057,7 @@ Use quotes to include spaces in a value. YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary sistem tepsisinde çalışmaya devam edecektir. Programı sonlandırmak için sistem tepsisi simgesinin bağlam menüsünden <b>Çık</b>'ı seçin. + YACReaderLibrary sistem tepsisinde çalışmaya devam edecektir. Programı sonlandırmak için sistem tepsisi simgesinin bağlam menüsünden <b>Çıkış</b>'ı seçin. @@ -4784,6 +6073,44 @@ Use quotes to include spaces in a value. Önceki sürümler + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Kitaplık veritabanı eksik. XML bilgilerini yeniden taramadan önce bir yedeği geri yükleyin veya kitaplığı yeniden oluşturun. + + + + Unable to load the archive backend: %1 + Arşiv arka ucu yüklenemedi: %1 + + + + Unable to start an XML rescan transaction. + XML yeniden tarama işlemi başlatılamadı. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Seçili klasör artık bu kitaplıkta değil. Yeniden taramadan önce kitaplığı yeniden yükleyin. + + + + Unable to read comics for the XML rescan. + XML yeniden taraması için çizgi romanlar okunamadı. + + + + Unable to commit XML metadata changes. + XML üst verisi değişiklikleri kalıcılaştırılamadı. + + + + Unable to update XML metadata for %1. + %1 için XML üst verisi güncellenemedi. + + YACReaderFieldEdit @@ -4793,7 +6120,7 @@ Use quotes to include spaces in a value. - + Click to overwrite Üstüne yazmak için tıkla @@ -4806,10 +6133,12 @@ Use quotes to include spaces in a value. Varsayılana dön + + - - - + + + Click to overwrite Üstüne yazmak için tıkla diff --git a/YACReaderLibrary/yacreaderlibrary_zh_CN.ts b/YACReaderLibrary/yacreaderlibrary_zh_CN.ts index f548f5450..b56cbb619 100644 --- a/YACReaderLibrary/yacreaderlibrary_zh_CN.ts +++ b/YACReaderLibrary/yacreaderlibrary_zh_CN.ts @@ -11,10 +11,70 @@ AddLabelDialog + + + red + 红色 + + + + orange + 橙色 + + + + yellow + 黄色 + + + + green + 绿色 + + + + cyan + 青色 + + + + blue + 蓝色 + + + + violet + 蓝紫色 + + + + purple + 紫色 + + + + pink + 粉色 + + + + white + 白色 + + + + light + 浅色 + + + + dark + 深色 + Choose a color: - 选择标签颜色: + 选择标签颜色: @@ -29,7 +89,7 @@ Label name: - 标签名称: + 标签名称: @@ -51,13 +111,13 @@ - Comics folder : - 漫画文件夹: + Comics folder: + 漫画文件夹: - Library name : - 库名: + Library name: + 库名: @@ -74,13 +134,13 @@ - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - 你需要拥有自己的API密钥才能够连接Comic Vine. 你可以通过这个链接获得一个免费的<a href="http://www.comicvine.com/api/">API</a>密钥 + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + 您需要拥有自己的 API 密钥才能连接 Comic Vine。您可以通过<a href="http://www.comicvine.com/api/">此链接</a>免费获取一个 API 密钥 Paste here your Comic Vine API key - 在此粘贴你的Comic Vine API + 在此粘贴您的 Comic Vine API 密钥 @@ -113,7 +173,7 @@ Remove - 消除 + 移除 @@ -133,7 +193,7 @@ Custom: - 自定义: + 自定义: @@ -200,132 +260,649 @@ - ClassicComicsView + CblEntriesView - - Hide comic flow - 隐藏漫画页面流 + + Find comic › + 查找漫画 › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - 复制漫画中... + + Series + 系列 - Moving comics... - 移动漫画中... + + Number + 编号 + + + + Volume + 卷 + + + + Year + 年 + + + + Format + 格式 + + + + + ComicVine ID + ComicVine ID + + + + series and number + 系列与编号 + + + + series without the volume, and number + 不含卷号与编号的系列 + + + + letters and digits of the series, and number + 该系列中的字母与数字,以及编号 + + + + series with its year, and number + 系列及其年份与编号 + + + + volume %1 + 卷 %1 + + + + Comic in your library + 你库里的漫画 + + + + Missing in your library + 你的库中缺失 + + + + Changed by you. + 被你更改。 + + + + The entry has this comic. + 该条目包含这本漫画。 + + + + Matched by %1. + 匹配 %1。 + + + + Your library already has this comic as missing. Matched by %1. + 你的库中已将此漫画标记为“缺失”。匹配 %1。 + + + + Possible comic %1 + 可能存在的漫画 %1 + + + + + Changed by you: it is imported as missing. + 被你更改:导入时被标记为缺失。 + + + + The comics have the same ComicVine ID. + 这些漫画拥有相同的 ComicVine ID。 + + + + The comic has the same series and number, but its volume or year is different. + 这本漫画所属的系列和编号相同,但卷数或年份不同。 + + + + The comics have the same series and number. + 这些漫画属于同一个系列,且编号相同。 + + + + Values that are different from the file are in amber. + 与文件不同的值以琥珀色显示。 + + + + Closest series in your library + 你库中最相似的系列 + + + + Library + 库 + + + + Has %1 + 包含 %1 + + + + No comic found. Tried: %1. + 未找到漫画。已尝试:%1。 + + + + In the file + 在文件中 + + + + Entry %1 of %2 + 条目 %1,共 %2 + + + + Use this comic + 使用这本漫画 + + + + Use this missing comic + 使用这本缺失的漫画 + + + + CblFindComicPage + + + ‹ All entries + ‹ 所有条目 + + + + Previous problem + 上一个问题 + + + + Next problem + 下一个问题 + + + + Folders + 文件夹 + + + + Search the whole library + 在整个库中搜索 + + + + File + 文件 + + + + Series + 系列 + + + + No. + No. + + + + Volume + 卷 + + + + Year + 年 + + + + Go to the next problem after I choose + 选择后,进入下一问题 + + + + Use this comic + 使用这本漫画 + + + + Cancel + 取消 + + + + Search in this folder + 在此文件夹中搜索 + + + + Entry %1 of %2 + 条目 %1,共 %2 + + + + Missing: no comic found. Browse or search the library. + 缺失:未找到漫画。请浏览或搜索库。 + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + 不确定:%n 本可能存在的漫画。请浏览或搜索库。 + + + + + Browse or search the library to choose another comic. + 浏览或搜索库,选择另一本漫画。 + + + + Problem %1 of %2 + 第 %1 个问题,共 %2 个问题 + + + + %n problem(s) + + %n 个问题 + + + + + + + Library + 库 + + + + Search results + 搜索结果 + + + + CblImportDialog + + + Match to library comics — %1 + 匹配到库漫画 — %1 + + + + Apply + 应用 + + + + Close + 关闭 + + + + Skip + 忽略 + + + + Import + 导入 + + + + &Name: + &名称: + + + + A reading list with this name already exists. Import creates another one. + 已存在同名的阅读列表。导入操作会再创建一个。 + + + + Filter entries + 筛选条目 + + + + Matching the books of %1 with your library... + 正在将 %1 中的书籍与您的库进行匹配... + + + + Matching the entries of %1 with your library... + 正在将 %1 中的条目与您的库进行匹配... + + + + + Match to library comics + 匹配到库漫画 + + + + This also gives comics to entries in %n other list(s): %1 + + 这还将这些漫画添加到了其他 %n 个列表中的条目里:%1 + + + + + The changes could not be applied. Nothing was changed. + 无法应用这些更改。未作任何更改。 + + + + The file cannot be opened. + 无法打开该文件。 + + + + The file is damaged (line %1, column %2). + 文件已损坏(第 %1 行,第 %2 列)。 + + + + The file is not a reading list. + 该文件并非阅读列表。 + + + + The library cannot be read. + 无法读取该库。 + + + + Import reading lists + 导入阅读列表 + + + + The reading list could not be imported. Nothing was written. + 无法导入阅读列表。未写入任何内容。 + + + + Import reading lists — %1 (%2 of %3) + 导入阅读列表 — %1(%2 共 %3) + + + + Import reading lists — %1 + 导入阅读列表 — %1 + + + + All (%1) + 全部 (%1) + + + + Matched (%1) + 匹配 (%1) + + + + Ambiguous (%1) + 不确定 (%1) + + + + Missing (%1) + 缺失 (%1) + + + + Changed by you (%1) + 被你更改 (%1) + + + + %1 of %n have a comic + + %1(共 %n)拥有漫画 + + + + + Find comic... + 查找漫画... + + + + Use the original match + 使用原始匹配 + + + + Make missing + 造成缺失 + + + + Import as missing + 作为缺失项导入 + + + + CblImportModel + + + Matched + 匹配 + + + + Ambiguous + 不确定 + + + + Missing + 缺失 + + + + Changed + 已更改 + + + + # + # + + + + In the file + 在文件中 + + + + Comic in your library + 你库里的漫画 + + + + Status + 状态 + + + + Already missing in your library + 你的库中尚缺此项 + + + + %n possible comic(s) + + %n 本可能存在的漫画 + + + + + Not in library + 不在库内 + + + + CblMatchComicDialog + + + Match to library comic + 匹配到库漫画 + + + + Loading the library... + 正在加载库... + + + + The library cannot be read. + 无法读取该库。 + + + + ClassicComicsView + + + Hide comic flow + 隐藏漫画流 ComicInfoView - + b/w 黑白 - + cover artist 封面画师 - + imprint - 压印 + 出版品牌 - + Teams 团队 - + color 彩色 - + inker 墨线师 - + Main character or team 主要角色或团队 - + penciller 铅笔画师 - + colorist 上色师 - + editor 编辑 - + writer 编剧 - + Characters 角色 - + Authors 作者 - + Publisher 出版社 - + Locations 地点 - + letterer - 字效师 + 嵌字师 ComicManagementCoordinator - - + + YACReader not found YACReader 未找到 - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. - 未找到YACReader. YACReader应安装在与YACReaderLibrary相同的文件夹中. + 未找到 YACReader。YACReader 应安装在与 YACReaderLibrary 相同的文件夹中。 - + YACReader not found. There might be a problem with your YACReader installation. - 未找到YACReader. YACReader的安装可能有问题. + 未找到 YACReader。YACReader 的安装可能有问题。 - + Error 错误 - + Error opening comic with third party reader. 使用第三方阅读器打开漫画时出错。 @@ -333,67 +910,67 @@ ComicModel - + no 否 - + yes 是 - + Read - 阅读 + 已读 - + Size 大小 - + Pages 页数 - + Title 标题 - + Current Page 当前页 - + File Name 文件名 - + Rating 评分 - + Series 系列 - + Volume 卷 - + Story Arc 故事线 - + Publication Date 出版日期 @@ -401,66 +978,107 @@ ComicVineDialog - + back 返回 - + next 下一步 - + skip - 忽略 + 跳过 + + + + close + 关闭 + + + + + Retrieving tags for: %1 + 正在检索标签:%1 + + + + Creating missing comic %1 of %2 + 正在创建缺失的漫画 %1共 %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine 未对 %n 本漫画做出响应。 + + + + + %n missing comic(s) got only the basic data of the volume. + + %n 缺失的漫画仅获取了卷的基本数据。 + + + + + %n missing comic(s) have no cover. + + %n 缺失的漫画没有封面。 + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine 每小时允许约 200 次请求;对于未更新的漫画,请稍后再试。 - - close - 关闭 + + Try again later for the comics that were not updated. + 对于未更新的漫画,请稍后再试。 - - - Retrieving tags for : %1 - 正在检索标签: %1 + + Comic Vine + Comic Vine - + Looking for comic... 搜索漫画中... - + search 搜索 - - - + + + Looking for volume... 搜索卷... - - + + comic %1 of %2 - %3 第 %1 本 共 %2 本 - %3 - + %1 comics selected 已选择 %1 本漫画 - - Error connecting to ComicVine - ComicVine 连接时出错 + + Error connecting to Comic Vine + Comic Vine 连接时出错 - + Retrieving volume info... 正在接收卷信息... @@ -492,23 +1110,23 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. 创建一个新的库可能需要几分钟时间,您可以先停止该进程,稍后可以通过更新库选项来更新数据。 The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - 所选路径不存在或不是有效路径. 确保您具有此文件夹的写入权限 + 所选路径不存在或不是有效路径。确保您具有此文件夹的写入权限 - Comics folder : - 漫画文件夹: + Comics folder: + 漫画文件夹: - Library Name : - 库名: + Library Name: + 库名: @@ -519,11 +1137,19 @@ DBHelper - + The folder entry could not be found in the library database. 在库数据库中找不到该文件夹的记录。 + + DelayedProgressDialog + + + Cancel + 取消 + + EditShortcutsDialog @@ -543,13 +1169,13 @@ - The shortcut "%1" is already assigned to other function + The shortcut "%1" is already assigned to another function 快捷键 "%1" 已被映射至其他功能 - To change a shortcut, double click in the key combination and type the new keys. - 更改快捷键: 双击按键组合并输入新的映射. + To change a shortcut, double-click the key combination and type the new keys. + 更改快捷键:双击按键组合并输入新的映射。 @@ -581,6 +1207,14 @@ 此标签尚未包含漫画 + + EmptyListFolderWidget + + + This folder is empty + 此文件夹为空 + + EmptyReadingListWidget @@ -599,7 +1233,7 @@ You are not reading anything yet, come on!! - 你还没有阅读任何东西,加油!! + 您还没有阅读任何东西,加油!! @@ -611,8 +1245,8 @@ ExportComicsInfoDialog - Output file : - 输出文件: + Output file: + 输出文件: @@ -632,7 +1266,7 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - 选定的输出文件路径不存在或路径无效. 确保您具有此文件夹的写入权限 + 选定的输出文件路径不存在或路径无效。确保您具有此文件夹的写入权限 @@ -660,12 +1294,12 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - 选定的输出文件路径不存在或路径无效. 确保您具有此文件夹的写入权限 + 选定的输出文件路径不存在或路径无效。确保您具有此文件夹的写入权限 - Output folder : - 输出文件夹: + Output folder: + 输出文件夹: @@ -680,42 +1314,31 @@ Destination directory - 目标目录 + 目标文件夹 FileComic - - + + Format not supported 不支持的文件格式 - + 7z not found 未找到 7z - + Unknown error opening the file 打开文件时出现未知错误 - Unsupported EPUB: %1 - 不支持的 EPUB 格式:%1 - - - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 页 CRC 校验失败: 部分页面将无法正确显示 - - - - FolderContentView - - Continue Reading... - 继续阅读... + 第 %1 页 CRC 校验失败:部分页面将无法正确显示 @@ -743,7 +1366,7 @@ Read - 阅读 + 已读 @@ -786,59 +1409,43 @@ Folder name: - 文件夹名称: + 文件夹名称: GridComicsView - + Show info 显示信息 - Library - 库 - - - Folder - 文件夹 - - - Favorites - 收藏夹 - - - Recently added - 最近添加 - - - + Manga 日式漫画 - + Western manga - 西式漫画 + 欧美漫画 - + Web comic 网络漫画 - + Yonkoma 四格漫画 - + Comic 漫画 - + Unknown 未知 @@ -880,8 +1487,8 @@ - Info database location : - 数据库地址: + Info database location: + 数据库地址: @@ -898,8 +1505,8 @@ ImportLibraryDialog - Destination folder : - 目标文件夹: + Destination folder: + 目标文件夹: @@ -913,18 +1520,18 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) 已压缩的库封面 (*.clc) - Package location : - 打包地址: + Package location: + 打包地址: - Library Name : - 库名: + Library Name: + 库名: @@ -952,16 +1559,16 @@ Upgrading the library - 正在更新库 + 正在升级库 <p>The current library is being upgraded, please wait.</p> - <p>正在更新当前漫画库, 请稍候.</p> + <p>正在更新当前漫画库,请稍候。</p> - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> <p>YACReaderLibrary现在正在创建一个新库。</p><p>这可能需要几分钟时间,您可以先停止该进程,稍后可以通过更新库选项来更新数据。</p> @@ -971,8 +1578,8 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>正在扫描当前库的旧版 XML metadata 信息。</p><p>这只需要执行一次,且只有当创建库的 YACReaderLibrary 版本低于 9.8.2 时。</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>正在扫描当前库的旧版 XML 元数据信息。</p><p>这只需要执行一次,且仅当该库由 YACReaderLibrary 9.8.2 或更早版本创建时才需要。</p> @@ -981,7 +1588,7 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> <p>正在更新当前库。要获得更快的更新,请经常更新您的库。</p><p>您可以停止该进程,稍后继续更新操作。</p> @@ -990,12 +1597,42 @@ <p>正在检查当前漫画库中缺失的封面和不完整的漫画信息。</p><p>这可能需要几分钟。您可以停止该过程,稍后再重新运行。</p> + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 无法创建书库数据文件夹:%1。请检查文件夹访问权限和可用磁盘空间。 + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 书库数据文件夹缺失:%1。请先恢复该文件夹或重新创建书库,再进行更新。 + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 书库数据库缺失:%1。请先恢复备份或重新创建书库,再进行更新。 + + + + + Unable to start a library database transaction: %1 + 无法启动书库数据库事务:%1 + + + + + Unable to commit the library database: %1 + 无法提交书库数据库事务:%1 + + LibraryInfoView Library info - 图书馆信息 + 库信息 @@ -1013,63 +1650,151 @@ 已读漫画数量 + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + 此书库正忙。请等待维护完成。如果似乎已卡住,请关闭“详细信息”中显示的应用程序,然后重试。 + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + 先前的维护操作可能未释放此书库。恢复访问前,请确保没有任何 YACReaderLibrary 或 YACReaderLibraryServer 操作正在使用它,包括其他计算机上的操作。 + + + + Retry + 重试 + + + + Recover and retry + 恢复并重试 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 书库锁的所有者已更改。请重试操作并检查新的所有者,然后再恢复访问。 + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 无法移除维护锁:%1。请确保文件不是只读的,且您可以写入书库数据文件夹,然后重试。 + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 维护锁文件路径被文件夹占用:%1。请先重命名或移动该文件夹,然后重试。 + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 无法创建书库锁文件:%1。请检查书库数据文件夹的写入权限。 + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 书库数据文件夹缺失:%1。请恢复该文件夹或重新创建书库,然后再更新。 + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 书库存在维护锁,但无法验证其所有者(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 另一个维护操作正在使用此书库(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 无法创建书库锁文件:%1。请检查可用磁盘空间和书库数据文件夹的访问权限。 + + LibraryManagementCoordinator - + Error opening the library 打开库时出错 - + Error creating the library 创建库时出错 - + Error updating the library 更新库时出错 + + + + XML information rescan + 重新扫描 XML 信息 + + + + Restore recovery + 恢复还原操作 + + + + Library upgrade + 书库升级 + + + + Library maintenance + 书库维护 + + + + Recover library access + 恢复书库访问 + LibraryWindow - + The selected folder doesn't contain any library. 所选文件夹不包含任何库。 - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - 此库是使用旧版本的YACReaderLibrary创建的. 它需要更新. 现在更新? + 此库是使用旧版本的YACReaderLibrary创建的。它需要更新。现在更新? - + Upgrade failed 更新失败 Folder name: - 文件夹名称: + 文件夹名称: The selected folder and all its contents will be deleted from your disk. Are you sure? - 所选文件夹及其所有内容将从磁盘中删除。 你确定吗? + 所选文件夹及其所有内容将从磁盘中删除。您确定吗? - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - 尝试删除所选文件夹时出现问题。 请检查写入权限,并确保没有其他应用程序在使用这些文件夹或文件。 - - - Remove and delete metadata - 移除并删除元数据 + 尝试删除所选文件夹时出现问题。请检查写入权限,并确保没有其他应用程序在使用这些文件夹或文件。 - + Old library 旧的库 @@ -1079,34 +1804,34 @@ 访问文件夹的路径时出错 - + Comics will only be deleted from the current label/list. Are you sure? - 漫画只会从当前标签/列表中删除。 你确定吗? + 漫画只会从当前标签/列表中删除。您确定吗? - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - 此库是使用较新版本的YACReaderLibrary创建的。 立即下载新版本? + 此库是使用较新版本的YACReaderLibrary创建的。立即下载新版本? - + Moving comics... 移动漫画中... - + Copying comics... 复制漫画中... - + Library '%1' is no longer available. Do you want to remove it? - 库 '%1' 不再可用。 你想删除它吗? + 库 '%1' 不再可用。您想删除它吗? - - Do you want remove - 你想要删除 + + Do you want to remove the library '%1'? + 您想要删除“%1”库? @@ -1114,22 +1839,22 @@ 路径错误 - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - 库 '%1' 是通过旧版本的YACReaderLibrary创建的。 必须再次创建。 你想现在创建吗? + 库 '%1' 是通过旧版本的YACReaderLibrary创建的。必须再次创建。您想现在创建吗? - + Save covers 保存封面 - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. 您添加的库太多了。 一般情况只需要一个顶级的库,您可以使用左侧边栏中的文件夹功能来进行分类管理。 @@ -1137,144 +1862,174 @@ YACReaderLibrary will not stop you from creating more libraries but you should k YACReaderLibrary不会阻止您创建更多的库,但是您应该保持较低的库数量来提升性能。 - + Assign comics numbers 分配漫画编号 - + There were errors during library upgrade in: - 漫画库更新时出现错误: + 漫画库更新时出现错误: - Please, select a folder first + Please select a folder first 请先选择一个文件夹 - + Library not available 库不可用 - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. - 尝试删除所选漫画时出现问题。 请检查所选文件或包含文件夹中的写入权限。 + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + 尝试删除所选漫画时出现问题。请检查所选文件或包含文件夹中的写入权限。 - + YACReader Library - YACReader 库 + YACReader Library - + You are adding too many libraries. 您添加的库太多了。 - + Update needed 需要更新 - + Library name already exists 库名已存在 - + There is another library with the name '%1'. - 已存在另一个名为'%1'的库。 + 已存在另一个名为 '%1' 的库。 + Delete folder 删除文件夹 - - Assign numbers starting in: - 从以下位置开始分配编号: + + Assign numbers starting at: + 起始编号: - + Download new version 下载新版本 - + + Edit smart list... + 编辑智能列表... + + + + Delete smart list + 删除智能列表 + + + + + + New folder... + 新建文件夹... + + + + + + Move to folder... + 移动到文件夹... + + + + Rename folder... + 重命名文件夹... + + + + Match to library comics... + 匹配到库漫画... + + + + Track missing issues... + 追踪缺失的期... + + + + Export reading list... + 导出阅读列表... + + + Rename or organize files 重命名或整理文件 - + Set the type of the selected comics 设置所选漫画的类型 - + + Save as smart list… + 另存为智能列表… + + + Search filters 搜索筛选条件 - + Unread 未读 - + In progress 阅读中 - + Highly rated 高评分 - + Recently added 最近添加 - + Search syntax… 搜索语法… - - A repair of this library is already running (%1). Wait for it to finish. - 此库的修复已在运行中(%1)。请等待其完成。 - - - - The library is locked by a repair that did not finish. - 库已被一个未完成的修复锁定。 - - - - The library is locked by a repair started by %1. - 库已被 %1 启动的修复锁定。 - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - 如果您确定没有其他修复正在运行,可以移除该锁定。移除锁定并继续? - - - + Package operation failed 打包操作失败 - + The covers package operation could not be completed. 封面包操作无法完成。 - + Restore recovery failed 恢复操作修复失败 @@ -1325,48 +2080,42 @@ Folder: %1 无法更新库数据库,磁盘上的文件夹重命名也无法撤销。现在需要手动更新该库。 - - + + YACReader library database (*.ydb) - YACReader 资料库数据库 (*.ydb) + YACReader 库数据库 (*.ydb) - + The library database backup was created at: %1 资料库数据库备份已创建于: %1 - + Unable to create the library database backup: %1 无法创建资料库数据库备份: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? 恢复前请关闭 YACReaderLibraryServer 以及正在使用此资料库的所有其他 YACReader 应用程序。是否继续? - + Restoring library database... 正在恢复资料库数据库... - + The current library database is invalid. Restore the selected backup anyway? 当前资料库数据库无效。仍要恢复所选备份吗? - - - The library maintenance lock may be stale. Remove it and retry? - 资料库维护锁可能已失效。是否移除并重试? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1375,71 +2124,77 @@ Restart YACReaderLibrary before attempting recovery again. 再次尝试恢复前,请重新启动 YACReaderLibrary。 - + The library database was restored successfully. Update the library now? 资料库数据库已成功恢复。是否立即更新资料库? - + Library database damaged 资料库数据库已损坏 - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. 资料库“%1”的数据库已损坏,因此无法执行常规更新、维护和备份。YACReader 可以尝试修复数据库。部分损坏的数据可能无法恢复。现有备份不会被更改。 - + Attempt repair 尝试修复 - + Restore a backup... 恢复备份... - + Repairing library database... 正在修复资料库数据库... - - + + + Library database repair 修复资料库数据库 - - Another maintenance operation is currently using this library. Try again after it finishes. - 另一个维护操作正在使用此资料库。请在其完成后重试。 + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + 是否检查书库“%1”的数据库,并在需要时尝试修复?现有备份不会更改。 + + + + Check and repair + 检查并修复 - + The library database is already valid. 资料库数据库已经有效。 - + Library database repaired 资料库数据库已修复 - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 已通过重建索引修复资料库数据库。损坏的原始文件已保存在: %1 - + Library database rebuilt 资料库数据库已重建 - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1450,7 +2205,7 @@ Update the library now? 是否立即更新资料库? - + The damaged original was preserved at: @@ -1461,12 +2216,12 @@ The damaged original was preserved at: %1 - + Library database repair failed 资料库数据库修复失败 - + The library database could not be repaired: %1%2 @@ -1477,41 +2232,71 @@ You can restore a backup from the Library menu or recreate the library. 您可以从“资料库”菜单恢复备份,或重新创建资料库。 - + Remove and delete metadata and backups 移除并删除元数据和备份 - + Library info - 图书馆信息 + 库信息 - + Invalid image 图片无效 - + The selected file is not a valid image. 所选文件不是有效图像。 - + + Error saving cover 保存封面时出错 - + + There was an error saving the cover image. 保存封面图像时出错。 - + + + Match to library comic + 匹配到库漫画 + + + + This also gives the comic to entries in %n other list(s): %1 + + 这还将此漫画添加到了其他 %n 个列表中的条目里:%1 + + + + + The comic could not be matched. Nothing was changed. + 无法匹配该漫画。没有任何改动。 + + + + The selected missing comics will be removed from this folder. Are you sure? + 所选的缺失漫画将从该文件夹中移除。确定吗? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + 所选漫画将从您的磁盘中删除,所选的缺失漫画也将从该文件夹中移除。您确定吗? + + + Delete comics 删除漫画 @@ -1523,38 +2308,43 @@ You can restore a backup from the Library menu or recreate the library. 没有选中的文件夹 - + All the selected comics will be deleted from your disk. Are you sure? - 所有选定的漫画都将从您的磁盘中删除。你确定吗? + 所有选定的漫画都将从您的磁盘中删除。您确定吗? - + + Remove the comics from Reading? Their reading progress will be reset. + 要将这些漫画从“继续阅读”中移除吗?它们的阅读进度将会被重置。 + + + + The comics will be removed from Favorites. Are you sure? + 漫画将从“收藏”中移除。确定吗? + + + Remove comics 移除漫画 - + Library not found 未找到库 - + Unable to delete 无法删除 - - library? - 库? - - - + Are you sure? - 你确定吗? + 您确定吗? - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1639,433 +2429,504 @@ Missing files: %3 + Repair library database... + 修复书库数据库... + + + + Check the current library database and attempt to repair any damage + 检查当前书库数据库并尝试修复损坏 + + + + Recover library access... + 恢复书库访问... + + + + Recover access after an interrupted library maintenance operation + 在书库维护操作中断后恢复访问 + + + Repair covers and comic info 修复封面和漫画信息 - + Retry comics with missing covers or incomplete information 重新处理缺少封面或信息不完整的漫画 - + Rename library 重命名库 - + Rename current library 重命名当前库 - + Remove library 移除库 - + Remove current library from your collection 从您的集合中移除当前库 - + Rescan library for XML info 重新扫描库的 XML 信息 - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. - 尝试查找漫画文件内嵌的 XML 信息。只有当创建库的 YACReaderLibrary 版本低于 9.8.2 或者使用第三方软件嵌入 XML 信息时,才需要执行该操作。 + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. + 尝试查找漫画文件内嵌的 XML 信息。只有当该库由 9.8.2 或更早版本创建,或者您使用第三方软件将 XML 信息嵌入文件时,才需要执行该操作。 - + Open library folder... 打开库文件夹... - + Open the root folder of the current library 打开当前库的根文件夹 - + Show library info - 显示图书馆信息 + 显示库信息 - + Show information about the current library 显示当前库的信息 - + Open current comic 打开当前漫画 - - Open current comic on YACReader + + Open current comic in YACReader 用YACReader打开漫画 - + Save selected covers to... - 选中的封面保存到... + 将选中的封面保存到... - + Save covers of the selected comics as JPG files - 保存所选的封面为jpg + 将所选漫画的封面保存为 JPG 文件 - - + + Set as read 设为已读 - + Set comic as read 漫画设为已读 - - + + Set as unread 设为未读 - + Set comic as unread 漫画设为未读 - - + + manga - 日本漫画 + 日式漫画 - + Set issue as manga - 设置为漫画 + 设置为日式漫画 - - + + comic 漫画 - + Set issue as normal - 设置漫画为 + 设置为普通漫画 - + western manga 欧美漫画 - + Set issue as western manga 设置为欧美漫画 - - + + web comic 网络漫画 - + Set issue as web comic 设置为网络漫画 - - + + yonkoma 四格漫画 - + Set issue as yonkoma 设置为四格漫画 - + Show/Hide marks 显示/隐藏标记 - + Show or hide read marks 显示或隐藏阅读标记 - + Show/Hide recent indicator 显示/隐藏最近的指示标志 - + Show or hide recent indicator 显示或隐藏最近的指示标志 - - + + Fullscreen mode on/off 全屏模式 开/关 - + Help, About YACReader - 帮助, 关于 YACReader + 帮助,关于 YACReader - + Add new folder 添加新的文件夹 - + Add new folder to the current library 在当前库下添加新的文件夹 - + Rename folder 重命名文件夹 - + Rename the current folder on disk and in the library 在磁盘和库中重命名当前文件夹 - + Delete folder 删除文件夹 - + Delete current folder from disk 从磁盘上删除当前文件夹 - + Select root node 选择根节点 - + Expand all nodes 展开所有节点 - + Collapse all nodes 折叠所有节点 - + Show options dialog 显示选项对话框 - + Show comics server options dialog 显示漫画服务器选项对话框 - - + + Change between comics views - 漫画视图之间的变化 + 切换漫画视图 + + + + Grid view + 网格视图 + + + + Flow view + 流视图 - + + Info view + 信息视图 + + + Open folder... 打开文件夹... - - + + Rename files... Organize files 重命名文件... - - + + Organize into folders... 整理到文件夹... - + Set as uncompleted 设为未完成 - + Set as completed 设为已完成 - + Set custom cover 设置自定义封面 - + Delete custom cover 删除自定义封面 - + western manga (left to right) 欧美漫画(从左到右) - + Open containing folder... - 打开包含文件夹... + 打开所在文件夹... - Reset comic rating - 重置漫画评分 + + New reading list + 新建阅读列表 - + Select all comics 全选漫画 - + Edit 编辑 - + Assign current order to comics 将当前序号分配给漫画 - + Update cover 更新封面 - + Delete selected comics 删除所选的漫画 - + Delete metadata from selected comics 从选定的漫画中删除元数据 - + Download tags from Comic Vine 从 Comic Vine 下载标签 - + Focus search line - 聚焦于搜索行 + 聚焦搜索栏 - + Focus comics view 聚焦于漫画视图 - + Edit shortcuts 编辑快捷键 - + &Quit 退出(&Q) - + Update folder 更新文件夹 - + Update current folder 更新当前文件夹 - + Scan legacy XML metadata 扫描旧版 XML 元数据 - - Add new reading list - 添加新的阅读列表 - - - + Add a new reading list to the current library 在当前库添加新的阅读列表 - + + New smart list... + 新建智能列表... + + + + Add a saved search to the current library + 将已保存的搜索添加到当前库 + + + + New folder... + 新建文件夹... + + + + Add a folder for reading lists or smart lists + 添加一个文件夹,用于存放阅读列表或智能列表 + + + + Import reading lists... + 导入阅读列表... + + + + Import CBL reading lists into the current library + 将 CBL 阅读列表导入当前库 + + + + New + 新的 + + + + Add or import reading lists + 添加或导入阅读列表 + + + Remove reading list 移除阅读列表 - + Remove current reading list from the library 从当前库移除阅读列表 - + Add new label 添加新标签 - + Add a new label to this library 在当前库添加标签 - + Rename selected list 重命名列表 - + Rename any selected labels or lists 重命名任何选定的标签或列表 - + Add to... 添加到... - + Favorites 收藏夹 - + Add selected comics to favorites list 将所选漫画添加到收藏夹列表 - - + + Reset rating 重置评分 @@ -2080,7 +2941,7 @@ Missing files: %3 manga - 日本漫画 + 日式漫画 @@ -2094,91 +2955,104 @@ Missing files: %3 - 4koma (top to botom) + 4koma (top to bottom) 四格漫画(从上到下) - - - + + + Set type 设置类型 - + Library 库 - + Folder 文件夹 - + Comic 漫画 - + + Match to library comic... + 匹配到库漫画... + + + + + + + Track missing issues... + 追踪缺失的期... + + + Open folder... 打开文件夹... - + Update folder 更新文件夹 - + Rename folder 重命名文件夹 - + Rename files... 重命名文件... - + Organize into folders... 整理到文件夹... - + Rescan library for XML info 重新扫描库的 XML 信息 - + Set as uncompleted 设为未完成 - + Set as completed 设为已完成 - + Set as read 设为已读 - - + + Set as unread 设为未读 - + Set custom cover 设置自定义封面 - - + + Delete custom cover 删除自定义封面 @@ -2219,26 +3093,72 @@ Missing files: %3 LocalComicListModel - + file name 文件名 + + MoveToFolderDialog + + + Move to folder + 移动到文件夹 + + + + Top level + 顶层 + + + + Move "%1" to: + 将 "%1" 移动到: + + + + NewListFolderDialog + + + New folder + 新建文件夹 + + + + Reading lists + 阅读列表 + + + + Smart lists + 智能列表 + + + + Folder name: + 文件夹名称: + + + + For: + 为: + + NoLibrariesWidget create your first library - 创建你的第一个库 + 创建您的第一个库 You don't have any libraries yet - 你还没有库 + 您还没有库 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> <p>您可以在任何文件夹中创建库,YACReaderLibrary将导入此文件夹中的所有漫画和文件夹。如果已有库,则可以打开它们。</p><p>您可以把YACReader当成独立应用来阅读电脑上的漫画。</p> @@ -2274,7 +3194,7 @@ Missing files: %3 - Restore defautls + Restore defaults 恢复默认值 @@ -2299,7 +3219,7 @@ Missing files: %3 - Start into the system tray + Start in the system tray 启动至系统托盘 @@ -2345,12 +3265,12 @@ Missing files: %3 Write {comic_file_path} where the path should go in the command - 在命令中应将路径写入 {comic_file_path} + 在命令中应出现路径的位置写入 {comic_file_path} Clear - 清空 + 清除 @@ -2370,7 +3290,7 @@ Missing files: %3 Blur level - 模糊 + 模糊程度 @@ -2415,17 +3335,17 @@ Missing files: %3 Comic Vine API key - Comic Vine API 密匙 + Comic Vine API 密钥 Edit Comic Vine API key - 编辑Comic Vine API 密匙 + 编辑 Comic Vine API 密钥 Tray icon settings (experimental) - 托盘图标设置 (实验特性) + 托盘图标设置(实验特性) @@ -2455,11 +3375,11 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. - 警告! 在库更新期间,将禁用对数据库的写入! + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. + 警告!在库更新期间,将禁用对数据库的写入! 当您可能正在积极使用该应用程序时,请勿安排更新。 在自动更新期间,应用程序将阻止某些操作,直到更新完成。 要停止自动更新,请点击库标题旁边的加载指示器。 @@ -2467,7 +3387,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Opacity level - 透明度 + 不透明度 @@ -2482,7 +3402,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Consider 'recent' items added or updated since X days ago - 参考自 X 天前添加或更新的“最近”项目 + 将自 X 天前起添加或更新的项目视为“最近”项目 @@ -2497,7 +3417,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Comic Flow - 漫画页面流 + 漫画流 @@ -2513,12 +3433,93 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFiles - + + + Unknown Series + 未知系列 + + + + series + 系列 + + + + Unknown Publisher + 未知出版社 + + + + publisher + 出版社 + + + + title + 标题 + + + + Unknown Comic + 未知漫画 + + + + Series #Number - Title + 系列 #编号 - 标题 + + + + Series #Number + 系列 #编号 + + + + Series #Number (of Count) + 系列 #编号(共 总数) + + + + Number - Title + 编号 - 标题 + + + + Series #Number (Year) + 系列 #编号(年份) + + + + Publisher / Series / Series #Number - Title + 出版社 / 系列 / 系列 #编号 - 标题 + + + + Publisher / Imprint / Series / Series #Number - Title + 出版社 / 出版品牌 / 系列 / 系列 #编号 - 标题 + + + + Series / Series #Number + 系列 / 系列 #编号 + + + + Publisher / Series / Series #Number (Year) + 出版社 / 系列 / 系列 #编号(年份) + + + + Series / original file name + 系列 / 原始文件名 + + + Renamed, %1 is already in use 已重命名,%1 已被占用 - + Missing metadata: %1 缺少元数据:%1 @@ -2527,62 +3528,82 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %1 could not be created 无法创建 %1 + + + %1 (copy also failed: %2) + %1(复制也失败:%2) + + + + the copy did not have the same size as the original + 副本与原件大小不同 + + + + the original could not be deleted after it was copied + 复制后无法删除原件 + + + + The destination folder could not be created. + 无法创建目标文件夹。 + + + + %1 (the file was left at %2) + %1(文件留在 %2) + OrganizeFilesCoordinator - - + + Organize files 整理文件 - + This folder does not contain any comics. 此文件夹不包含任何漫画。 - - This library is busy: %1 - 此库正忙:%1 - - - + the library database could not be opened 无法打开库数据库 - + the library database could not be locked for writing 无法锁定库数据库以进行写入 - + a folder entry could not be restored 无法恢复某个文件夹记录 - + a comic entry could not be updated 无法更新某条漫画记录 - + the library database could not be saved: %1 无法保存库数据库:%1 - + the record of the last organize run could not be read 无法读取上次整理的记录 - + the folder %1 could not be created 无法创建文件夹 %1 - + %n file(s) could not be moved back 有 %n 个文件无法移回 @@ -2591,10 +3612,6 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFilesDialog - - Format: - 格式: - Organize files @@ -2726,19 +3743,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Cancel 取消 - - Copy the list - 复制列表 - Undo 撤销 - - Close - 关闭 - Copy failure details @@ -2772,7 +3781,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Preset name: - 预设名称: + 预设名称: @@ -2787,7 +3796,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t new folder - 新文件夹 + 新建文件夹 @@ -3142,276 +4151,385 @@ To stop an automatic update tap on the loading indicator next to the Libraries t PropertiesDialog - - + + of: - 的: + 共: - + Day: - 日: + 日: - + Plot 情节 - + Notes - 笔记 + 备注 - + Load previous page as cover 加载上一页作为封面 - + Load next page as cover 加载下一页作为封面 - + Reset cover to the default image 将封面重置为默认图像 - + Load custom cover image 加载自定义封面图片 - + + e.g. 1-5, 8, 0.5, Annual 1 + 例如. 1-5, 8, 0.5, 年度 1 + + + Size: - 大小: + 大小: - + Tags: 标签: - + + + Track missing issues in "%1" + 追踪 “%1” 中缺失的期 + + + + Issues: + 期: + + + + Track + 追踪 + + + + At most %1 issues can be tracked at once. + 最多可同时追踪 %1 个期。 + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + 检查期:范围必须呈递增状态,且逗号之间的部分不能为空。 + + + + No new issues to track. + 没有新的期需要追踪。 + + + + %n issue(s) will be tracked: %1. + + 将追踪 %n 个期:%1。 + + + + + That is a lot of issues; check the numbers. + 期很多,查看一下编号。 + + + + %n issue(s) already in the folder. + + 文件夹中已有 %n 个期。 + + + + + %n repeated issue(s). + + %n 个重复出现的期。 + + + + + Set a series, a volume, or a title. + 设置系列、卷或标题。 + + + + Set a series or a volume. + 设置系列或卷。 + + + + Issues not tracked + 未追踪的期 + + + + The missing issues could not be tracked. + 无法追踪到缺失的期。 + + + + Comic skipped + 已跳过漫画 + + + + A comic was removed from the library or now has a different file, so it is skipped. + 某本漫画已从库中移除,或者其对应的文件发生了改变,因此该漫画被跳过。 + + + + + Changes not saved + 更改未保存 + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + 您正在编辑的漫画已从库中删除或现在有不同的文件。您的更改未保存。 + + + + The changes could not be saved. + 无法保存更改。 + + + Invalid cover 封面无效 - + The image is invalid. 该图像无效。 - + Type: 类型: - + Year: - 年: + 年: - + Alternate series: 备用系列: - + Inker(s): - 上墨师: + 上墨师: - + Publishing 出版 - + Publisher: - 出版商: + 出版社: - + General info 基本信息 - + alt. number: 备选编号: - + Color/BW: - 彩色/黑白: + 彩色/黑白: - + Edit selected comics information 编辑选中的漫画信息 - + Penciller(s): - 线稿师: + 线稿师: - + Colorist(s): - 上色师: + 上色师: - + Imprint: - 压印: + 出版品牌: - + Genre: - 类型: + 题材: - + + Issue number: - 期刊号: + 期号: - + Month: - 月: + 月: - + Notes: - 笔记: + 备注: - + Synopsis: - 简介: + 简介: - + Teams: 团队: - + Title: - 标题: + 标题: - + Arc number: - 世界线数量: + 故事线编号: - + Language (ISO): 语言(ISO): - + Not found 未找到 - + Characters: - 角色: + 角色: - + Main character or team: 主要角色或团队: - + Locations: 地点: - + Authors 作者 - + Review: - 审查: + 评论: - + Age rating: - 年龄分级: + 年龄分级: - + Story arc: - 故事线: + 故事线: - + Series: - 系列: + 系列: - + Writer(s): - 作者: + 编剧: - + Comic not found. You should update your library. - 未找到漫画,请先更新您的库. + 未找到漫画,请先更新您的库。 - + Editor(s): 编辑: - + Edit comic information 编辑漫画信息 - + Cover page 封面 - + Cover Artist(s): - 封面设计: + 封面设计: - + Series Group: 系列组: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - Comic Vine 连接: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 查看 </a> + Comic Vine 链接:<a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 查看 </a> - + Volume: - 卷: + 卷: - + Format: - 格式: + 格式: - + Letterer(s): - 嵌字师: + 嵌字师: @@ -3424,15 +4542,15 @@ To stop an automatic update tap on the loading indicator next to the Libraries t unable to load 7z lib from ./utils - 无法从 ./utils 载入 7z 库文件 + 无法从 ./utils 加载 7z 库文件 - + Select custom cover 选择自定义封面 - + Images (%1) 图片 (%1) @@ -3465,31 +4583,164 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists 添加新的阅读列表 - - + + List name: - 列表名称: + 列表名称: + + + + Import reading lists + 导入阅读列表 + + + + Reading lists (*.cbl *.json);;All files (*) + 阅读列表 (*.cbl *.json);;所有文件 (*) + + + + New smart list + 新建智能列表 + + + + + The smart list could not be saved. + 无法保存智能列表。 + + + + Edit smart list + 编辑智能列表 + + + + Delete folder + 删除文件夹 + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + 要删除文件夹 “%1” 及其中的所有内容吗?其中的文件夹和列表会被删除,但您的漫画文件不会受到影响。 - + Delete list/label 删除 列表/标签 - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - 所选项目将被删除,您的漫画或文件夹将不会从您的磁盘中删除。 你确定吗? + 所选项目将被删除,您的漫画或文件夹将不会从您的磁盘中删除。您确定吗? - + Rename list name 重命名列表 + + + Rename folder + 重命名文件夹 + + + + + Folder name: + 文件夹名称: + + + + + + New folder + 新建文件夹 + + + + + The folder could not be created. + 无法创建文件夹。 + + + + Reading list + 阅读列表 + + + + CBL reading list (*.cbl) + CBL 阅读列表 (*.cbl) + + + + JSON reading list (*.json) + JSON 阅读列表 (*.json) + + + + Web page (*.html) + 网页 (*.html) + + + + + Export reading list + 导出阅读列表 + + + + Exporting reading list... + 正在导出阅读列表... + + + + The file could not be written. + 无法写入文件。 + + + + Move to folder + 移动到文件夹 + + + + "%1" could not be moved. + 无法移动 “%1”。 + + + + ReadingListPage + + + %n comic(s) + + %n 本漫画 + + + + + %n missing + + %n 个缺失 + + + + + Missing + 缺失 + + + + Exported from YACReader on %1 + 从 YACReader 导出 %1 + RenameLibraryDialog @@ -3510,16 +4761,16 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - New Library Name : - 新库名: + New Library Name: + 新库名: ScraperResultsPaginator - Number of %1 found : %2 - 第 %1 页 共: %2 条 + Number of %1 found: %2 + 找到的 %1 数量:%2 @@ -3529,8 +4780,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of volumes found : %1 - 搜索结果: %1 + Number of volumes found: %1 + 找到的卷数:%1 @@ -3693,7 +4944,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Comic genre - 漫画类型 + 漫画题材 @@ -3812,12 +5063,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Please provide some additional information for this comic. Please provide some additional information. - 请提供附加信息. + 请提供附加信息。 Series: - 系列: + 系列: @@ -3892,6 +5143,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. 直接开始输入即可。纯文本会搜索所有元数据。 + + + %1 -> search for `%1` in any database field + %1 -> ??????????? `%1` + 1. Search everywhere @@ -3987,7 +5243,9 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - 提示:空格等同于 AND,搜索不区分大小写。使用引号可在一个值中包含空格。 + 提示: +空格等同于 AND,搜索不区分大小写。 +使用引号可在一个值中包含空格。 @@ -4022,7 +5280,7 @@ Use quotes to include spaces in a value. Examples show the pattern—replace the values with your own. - 示例展示了格式,请将值替换为你自己的值。 + 示例展示了格式,请将值替换为您自己的值。 @@ -4125,12 +5383,12 @@ Use quotes to include spaces in a value. Please provide some additional information. - 请提供附加信息. + 请提供附加信息。 Series: - 系列: + 系列: @@ -4162,8 +5420,8 @@ Use quotes to include spaces in a value. - Please, select the right comic info. - 请正确选择漫画信息. + Please select the right comic info. + 请正确选择漫画信息。 @@ -4180,7 +5438,7 @@ Use quotes to include spaces in a value. - Please, select the right series for your comic. + Please select the right series for your comic. 请选择正确的漫画系列。 @@ -4223,38 +5481,43 @@ Use quotes to include spaces in a value. %1 系列 - + %1 comics selected %n comic(s) selected 已选择 %1 本漫画 - + Read - 阅读 + 已读 - + In progress 阅读中 - + Unread 未读 - + Total pages 总页数 - + Total size 总大小 - + + Missing + 缺失 + + + Series 系列 @@ -4273,8 +5536,8 @@ Use quotes to include spaces in a value. - You are trying to get information for various comics at once, are they part of the same series? - 您正在尝试同时获取各种漫画的信息,它们是同一系列的吗? + You are trying to get information for various comics at once. Are they part of the same series? + 您正在尝试同时获取多本漫画的信息,它们是同一系列的吗? @@ -4331,57 +5594,72 @@ Use quotes to include spaces in a value. YACReader 支持 iOS 和 Android。获取 <a href='https://ios.yacreader.com'>iOS</a> 或 <a href='https://android.yacreader.com'>Android</a> 版本。 - enable the server - 启用服务器 + + Set port + set port + 设置端口 + + + SmartListDialog - Server connectivity information - 服务器连接信息 + + New smart list + 新建智能列表 - Scan it! - 扫一扫! + + Edit smart list + 编辑智能列表 - - Set port - set port - 设置端口 + + Search filters and syntax + 搜索筛选条件与语法 - Choose an IP address - 选择IP地址 + + Name: + 名称: - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader 适用于 iOS 和 Android 设备。<br/>搜索 <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. + + Search: + 搜索: SortVolumeComics - + remove selected comics 移除所选漫画 - + sort comics to match comic information 排序漫画以匹配漫画信息 - + restore all removed comics 恢复所有移除的漫画 + + + Add the %n other issue(s) of this volume as missing comics + + 将本卷中的其他 %n 期内容添加为缺失漫画 + + - + issues 期 - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. 请在左侧对漫画列表进行排序,直到它与漫画的信息相符。 @@ -4405,7 +5683,7 @@ Use quotes to include spaces in a value. i - 我 + i @@ -4420,7 +5698,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - 按住可在 UI 中闪烁所选值(洋红色/切换/0↔10)。发布后恢复原样。 + 按住可在界面中闪烁所选值(洋红色/切换/0↔10)。松开后恢复原样。 @@ -4430,12 +5708,12 @@ Use quotes to include spaces in a value. Light - 亮度 + 明亮 Dark - 黑暗的 + 暗黑 @@ -4450,7 +5728,7 @@ Use quotes to include spaces in a value. Variant: - 颜色设置: + 变体: @@ -4460,7 +5738,7 @@ Use quotes to include spaces in a value. Parameter - 范围 + 参数 @@ -4604,10 +5882,18 @@ Use quotes to include spaces in a value. - Updating.... + Updating... 更新中... + + ViewSelector + + + View + 视图 + + VolumeComicsModel @@ -4631,7 +5917,7 @@ Use quotes to include spaces in a value. publisher - 出版者 + 出版社 @@ -4639,7 +5925,7 @@ Use quotes to include spaces in a value. Presets: - 预设: + 预设: @@ -4674,7 +5960,7 @@ Use quotes to include spaces in a value. Custom: - 自定义: + 自定义: @@ -4719,12 +6005,12 @@ Use quotes to include spaces in a value. Visibility - 透明度 + 可见度 Light - 亮度 + 光照 @@ -4743,13 +6029,13 @@ Use quotes to include spaces in a value. - Use VSync (improve the image quality in fullscreen mode, worse performance) - 使用VSync (在全屏模式下提高图像质量, 性能更差) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + 使用 VSync(在全屏模式下提高图像质量,但会降低性能) Performance: - 性能: + 性能: @@ -4757,7 +6043,7 @@ Use quotes to include spaces in a value. &Restore - 复位(&R) + 恢复(&R) @@ -4767,7 +6053,7 @@ Use quotes to include spaces in a value. YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary 将继续在系统托盘中运行. 想要终止程序, 请在系统托盘图标的上下文菜单中选择<b>退出</b>. + YACReaderLibrary 将继续在系统托盘中运行。想要终止程序,请在系统托盘图标的上下文菜单中选择<b>退出</b>。 @@ -4783,6 +6069,44 @@ Use quotes to include spaces in a value. 以前的版本 + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 书库数据库缺失。请先恢复备份或重新创建书库,再重新扫描 XML 信息。 + + + + Unable to load the archive backend: %1 + 无法加载压缩包后端:%1 + + + + Unable to start an XML rescan transaction. + 无法启动 XML 重新扫描事务。 + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 选中的文件夹已不在此书库中。请重新加载书库后再扫描。 + + + + Unable to read comics for the XML rescan. + 无法读取用于 XML 重新扫描的漫画。 + + + + Unable to commit XML metadata changes. + 无法提交 XML 元数据更改。 + + + + Unable to update XML metadata for %1. + 无法更新 %1 的 XML 元数据。 + + YACReaderFieldEdit @@ -4792,7 +6116,7 @@ Use quotes to include spaces in a value. - + Click to overwrite 点击以覆盖 @@ -4805,10 +6129,12 @@ Use quotes to include spaces in a value. 恢复默认 + + - - - + + + Click to overwrite 点击以覆盖 @@ -4856,7 +6182,7 @@ Use quotes to include spaces in a value. type to search - 搜索类型 + 输入以搜索 diff --git a/YACReaderLibrary/yacreaderlibrary_zh_HK.ts b/YACReaderLibrary/yacreaderlibrary_zh_HK.ts index cae72bb0f..06d1198b1 100644 --- a/YACReaderLibrary/yacreaderlibrary_zh_HK.ts +++ b/YACReaderLibrary/yacreaderlibrary_zh_HK.ts @@ -11,15 +11,75 @@ AddLabelDialog + + + red + 紅色 + + + + orange + 橙色 + + + + yellow + 黃色 + + + + green + 綠色 + + + + cyan + 青色 + + + + blue + 藍色 + + + + violet + 紫羅蘭色 + + + + purple + 紫色 + + + + pink + 粉紅色 + + + + white + 白色 + + + + light + 淺色 + + + + dark + 深色 + Label name: - 標籤名稱: + 標籤名稱: Choose a color: - 選擇標籤顏色: + 選擇標籤顏色: @@ -36,19 +96,19 @@ AddLibraryDialog - Comics folder : - 漫畫檔夾: + Comics folder: + 漫畫資料夾: - Library name : + Library name: Library Name : - 庫名: + 庫名: Add - 添加 + 新增 @@ -58,20 +118,20 @@ Add an existing library - 添加一個現有庫 + 新增一個現有庫 ApiKeyDialog - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - 你需要擁有自己的API密鑰才能夠連接Comic Vine. 你可以通過這個鏈接獲得一個免費的<a href="http://www.comicvine.com/api/">API</a>密鑰 + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + 您需要擁有自己的 API 密鑰才能連線 Comic Vine。您可以通過<a href="http://www.comicvine.com/api/">此連結</a>免費獲得一個 API 密鑰 Paste here your Comic Vine API key - 在此粘貼你的Comic Vine API + 在此貼上您的 Comic Vine API 密鑰 @@ -99,22 +159,22 @@ Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 Custom - 風俗 + 自訂 Remove - 消除 + 移除 @@ -124,22 +184,22 @@ Light: - 光: + 淺色: Dark: - 黑暗的: + 深色: Custom: - 自定義: + 自訂: Import theme... - 導入主題... + 匯入主題... @@ -169,7 +229,7 @@ Import theme - 導入主題 + 匯入主題 @@ -197,107 +257,624 @@ Import failed - 導入失敗 + 匯入失敗 - ClassicComicsView + CblEntriesView - - Hide comic flow - 隱藏 Comic Flow + + Find comic › + 尋找漫畫 › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - 複製漫畫中... + + Series + 系列 - Moving comics... - 移動漫畫中... + + Number + 編號 + + + + Volume + 卷 + + + + Year + 年份 + + + + Format + 格式 + + + + + ComicVine ID + Comic Vine ID + + + + series and number + 系列和編號 + + + + series without the volume, and number + 不含卷號的系列和編號 + + + + letters and digits of the series, and number + 系列中的字母和數字及編號 + + + + series with its year, and number + 含年份的系列和編號 + + + + volume %1 + 第 %1 卷 + + + + Comic in your library + 書庫中的漫畫 + + + + Missing in your library + 書庫中缺少 + + + + Changed by you. + 已由您修改。 + + + + The entry has this comic. + 此項目已關聯此漫畫。 + + + + Matched by %1. + 根據%1配對。 + + + + Your library already has this comic as missing. Matched by %1. + 書庫中已將此漫畫標記為缺少。根據%1配對。 + + + + Possible comic %1 + 可能的漫畫 %1 + + + + + Changed by you: it is imported as missing. + 已由您修改:將作為缺少的漫畫匯入。 + + + + The comics have the same ComicVine ID. + 漫畫的 Comic Vine ID 相同。 + + + + The comic has the same series and number, but its volume or year is different. + 漫畫的系列和編號相同,但卷號或年份不同。 + + + + The comics have the same series and number. + 漫畫的系列和編號相同。 + + + + Values that are different from the file are in amber. + 與檔案不同的值以琥珀色顯示。 + + + + Closest series in your library + 書庫中最相近的系列 + + + + Library + 庫 + + + + Has %1 + 包含 %1 + + + + No comic found. Tried: %1. + 找不到漫畫。已嘗試:%1。 + + + + In the file + 檔案中的資訊 + + + + Entry %1 of %2 + 項目 %1 / %2 + + + + Use this comic + 使用此漫畫 + + + + Use this missing comic + 使用此缺少的漫畫 + + + + CblFindComicPage + + + ‹ All entries + ‹ 所有項目 + + + + Previous problem + 上一個問題 + + + + Next problem + 下一個問題 + + + + Folders + 資料夾 + + + + Search the whole library + 搜尋整個書庫 + + + + File + 檔案 + + + + Series + 系列 + + + + No. + 編號 + + + + Volume + 卷 + + + + Year + 年份 + + + + Go to the next problem after I choose + 選擇後前往下一個問題 + + + + Use this comic + 使用此漫畫 + + + + Cancel + 取消 + + + + Search in this folder + 在此資料夾中搜尋 + + + + Entry %1 of %2 + 項目 %1 / %2 + + + + Missing: no comic found. Browse or search the library. + 缺少:找不到漫畫。請瀏覽或搜尋書庫。 + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + 不明確:有 %n 部可能的漫畫。請瀏覽或搜尋書庫。 + + + + + Browse or search the library to choose another comic. + 請瀏覽或搜尋書庫以選擇其他漫畫。 + + + + Problem %1 of %2 + 問題 %1 / %2 + + + + %n problem(s) + + %n 個問題 + + + + + + + Library + 庫 + + + + Search results + 搜尋結果 + + + + CblImportDialog + + + Match to library comics — %1 + 配對書庫中的漫畫 — %1 + + + + Apply + 套用 + + + + Close + 關閉 + + + + Skip + 略過 + + + + Import + 匯入 + + + + &Name: + 名稱(&N): + + + + A reading list with this name already exists. Import creates another one. + 已存在同名閱讀清單。匯入將建立另一個清單。 + + + + Filter entries + 篩選項目 + + + + Matching the books of %1 with your library... + 正在將 %1 中的書籍與書庫配對... + + + + Matching the entries of %1 with your library... + 正在將 %1 中的項目與書庫配對... + + + + + Match to library comics + 配對書庫中的漫畫 + + + + This also gives comics to entries in %n other list(s): %1 + + 這也會為其他 %n 個清單中的項目關聯漫畫:%1 + + + + + The changes could not be applied. Nothing was changed. + 無法套用變更。未作任何變更。 + + + + The file cannot be opened. + 無法開啟檔案。 + + + + The file is damaged (line %1, column %2). + 檔案已損毀(第 %1 行,第 %2 欄)。 + + + + The file is not a reading list. + 此檔案不是閱讀清單。 + + + + The library cannot be read. + 無法讀取書庫。 + + + + Import reading lists + 匯入閱讀清單 + + + + The reading list could not be imported. Nothing was written. + 無法匯入閱讀清單。未寫入任何內容。 + + + + Import reading lists — %1 (%2 of %3) + 匯入閱讀清單 — %1(%2 / %3) + + + + Import reading lists — %1 + 匯入閱讀清單 — %1 + + + + All (%1) + 全部(%1) + + + + Matched (%1) + 已配對(%1) + + + + Ambiguous (%1) + 不明確(%1) + + + + Missing (%1) + 缺少(%1) + + + + Changed by you (%1) + 已由您修改(%1) + + + + %1 of %n have a comic + + %n 個中有 %1 個已關聯漫畫 + + + + + Find comic... + 尋找漫畫... + + + + Use the original match + 使用原始配對 + + + + Make missing + 標記為缺少 + + + + Import as missing + 作為缺少的漫畫匯入 + + + + CblImportModel + + + Matched + 已配對 + + + + Ambiguous + 不明確 + + + + Missing + 缺少 + + + + Changed + 已修改 + + + + # + # + + + + In the file + 檔案中的資訊 + + + + Comic in your library + 書庫中的漫畫 + + + + Status + 狀態 + + + + Already missing in your library + 書庫中已標記為缺少 + + + + %n possible comic(s) + + %n 部可能的漫畫 + + + + + Not in library + 不在書庫中 + + + + CblMatchComicDialog + + + Match to library comic + 配對書庫中的漫畫 + + + + Loading the library... + 正在載入書庫... + + + + The library cannot be read. + 無法讀取書庫。 + + + + ClassicComicsView + + + Hide comic flow + 隱藏 Comic Flow ComicInfoView - + Characters 角色 - + Main character or team 主要角色或團隊 - + Teams 團隊 - + Locations 地點 - + Authors 作者 - + writer 編劇 - + penciller 鉛筆畫師 - + inker 墨線師 - + colorist 上色師 - + letterer - 字效師 + 嵌字師 - + cover artist 封面畫師 - + editor 編輯 - + imprint 出版品牌 - + Publisher 出版社 - + color 彩色 - + b/w 黑白 @@ -305,28 +882,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader 未找到 - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. - 未找到YACReader. YACReader應安裝在與YACReaderLibrary相同的檔夾中. + 未找到 YACReader。YACReader 應安裝在與 YACReaderLibrary 相同的資料夾中。 - + YACReader not found. There might be a problem with your YACReader installation. - 未找到YACReader. YACReader的安裝可能有問題. + 未找到 YACReader。YACReader 的安裝可能有問題。 - + Error 錯誤 - + Error opening comic with third party reader. 使用第三方閱讀器開啟漫畫時出錯。 @@ -334,67 +911,67 @@ ComicModel - + yes 是 - + no 否 - + Title 標題 - + File Name 檔案名 - + Pages 頁數 - + Size 大小 - + Read - 閱讀 + 已讀 - + Current Page - 當前頁 + 目前頁 - + Publication Date 發行日期 - + Rating 評分 - + Series 系列 - + Volume - 體積 + 卷 - + Story Arc 故事線 @@ -402,68 +979,109 @@ ComicVineDialog - + skip - 忽略 + 跳過 - + back 返回 - + next 下一步 - + search - 搜索 + 搜尋 - + close 關閉 - - - + + + Looking for volume... - 搜索卷... + 搜尋卷... - - + + comic %1 of %2 - %3 第 %1 本 共 %2 本 - %3 - + %1 comics selected 已選擇 %1 本漫畫 - - Error connecting to ComicVine - ComicVine 連接時出錯 + + Error connecting to Comic Vine + Comic Vine 連線時出錯 + + + + + Retrieving tags for: %1 + 正在檢索標籤:%1 + + + + Creating missing comic %1 of %2 + 正在建立缺少的漫畫 %1 / %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine 未回應 %n 部漫畫的請求。 + + + + + %n missing comic(s) got only the basic data of the volume. + + %n 部缺少的漫畫僅取得卷的基本資訊。 + + + + + %n missing comic(s) have no cover. + + %n 部缺少的漫畫沒有封面。 + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine 每小時允許約 200 次請求;請稍後重試未更新的漫畫。 - - - Retrieving tags for : %1 - 正在檢索標籤: %1 + + Try again later for the comics that were not updated. + 請稍後重試未更新的漫畫。 - + + Comic Vine + Comic Vine + + + Retrieving volume info... 正在接收卷資訊... - + Looking for comic... - 搜索漫畫中... + 搜尋漫畫中... @@ -478,18 +1096,18 @@ CreateLibraryDialog - Comics folder : - 漫畫檔夾: + Comics folder: + 漫畫資料夾: - Library Name : - 庫名: + Library Name: + 庫名: Create - 創建 + 建立 @@ -498,13 +1116,13 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - 創建一個新的庫可能需要幾分鐘時間,您可以先停止該進程,稍後可以通過更新庫選項來更新數據。 + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + 建立一個新的庫可能需要幾分鐘時間,您可以先停止該處理程序,稍後可以通過更新庫選項來更新資料。 Create new library - 創建新的漫畫庫 + 建立新的漫畫庫 @@ -514,15 +1132,23 @@ The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - 所選路徑不存在或不是有效路徑. 確保您具有此檔夾的寫入許可權 + 所選路徑不存在或不是有效路徑。確保您具有此資料夾的寫入權限 DBHelper - + The folder entry could not be found in the library database. - 在庫資料庫中找不到該檔夾的記錄。 + 在庫資料庫中找不到該資料夾的記錄。 + + + + DelayedProgressDialog + + + Cancel + 取消 @@ -530,17 +1156,17 @@ Restore defaults - 恢復默認 + 恢復預設 - To change a shortcut, double click in the key combination and type the new keys. - 更改快捷鍵: 雙擊按鍵組合並輸入新的映射. + To change a shortcut, double-click the key combination and type the new keys. + 更改快捷鍵:雙擊按鍵組合並輸入新的映射。 Shortcuts settings - 快捷鍵設置 + 快捷鍵設定 @@ -549,8 +1175,8 @@ - The shortcut "%1" is already assigned to other function - 快捷鍵 "%1" 已被映射至其他功能 + The shortcut "%1" is already assigned to another function + 快捷鍵「%1」已被映射至其他功能 @@ -571,7 +1197,7 @@ Select a comic or folder to see its information. - 選取漫畫或資料夾以查看其資訊。 + 選取漫畫或資料夾以檢視其資訊。 @@ -582,6 +1208,14 @@ 此標籤尚未包含漫畫 + + EmptyListFolderWidget + + + This folder is empty + 此資料夾是空的 + + EmptyReadingListWidget @@ -601,7 +1235,7 @@ You are not reading anything yet, come on!! - 你還沒有閱讀任何東西,加油!! + 您還沒有閱讀任何東西,加油!! @@ -613,13 +1247,13 @@ ExportComicsInfoDialog - Output file : - 輸出檔: + Output file: + 輸出檔案: Create - 創建 + 建立 @@ -629,7 +1263,7 @@ Export comics info - 導出漫畫資訊 + 匯出漫畫資訊 @@ -644,20 +1278,20 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - 選定的輸出檔路徑不存在或路徑無效. 確保您具有此檔夾的寫入許可權 + 選定的輸出檔案路徑不存在或路徑無效。確保您具有此資料夾的寫入權限 ExportLibraryDialog - Output folder : - 輸出檔夾: + Output folder: + 輸出資料夾: Create - 創建 + 建立 @@ -667,7 +1301,7 @@ Create covers package - 創建封面包 + 建立封面包 @@ -677,43 +1311,36 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - 選定的輸出檔路徑不存在或路徑無效. 確保您具有此檔夾的寫入許可權 + 選定的輸出檔案路徑不存在或路徑無效。確保您具有此資料夾的寫入權限 Destination directory - 目標目錄 + 目標資料夾 FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 頁 CRC 校驗失敗: 部分頁面將無法正確顯示 + 第 %1 頁 CRC 校驗失敗:部分頁面將無法正確顯示 - + Unknown error opening the file - 打開檔時出現未知錯誤 + 開啟檔案時出現未知錯誤 - + 7z not found 未找到 7z - - + + Format not supported - 不支持的檔格式 - - - - FolderContentView - - Continue Reading... - 繼續閱讀... + 不支援的檔案格式 @@ -741,7 +1368,7 @@ Read - 閱讀 + 已讀 @@ -779,64 +1406,48 @@ Add new folder - 添加新的檔夾 + 新增新的資料夾 Folder name: - 檔夾名稱: + 資料夾名稱: GridComicsView - + Show info 顯示資訊 - Library - 庫 - - - Folder - 檔夾 - - - Favorites - 收藏夾 - - - Recently added - 最近新增 - - - + Manga 日式漫畫 - + Western manga - 西式漫畫 + 西方漫畫 - + Web comic 網絡漫畫 - + Yonkoma 四格漫畫 - + Comic 漫畫 - + Unknown 未知 @@ -869,17 +1480,17 @@ Import comics info - 導入漫畫資訊 + 匯入漫畫資訊 - Info database location : - 資料庫地址: + Info database location: + 資訊資料庫位置: Import - 導入 + 匯入 @@ -889,25 +1500,25 @@ Comics info file (*.ydb) - 漫畫資訊檔(*.ydb) + 漫畫資訊檔案(*.ydb) ImportLibraryDialog - Library Name : - 庫名: + Library Name: + 庫名: - Package location : - 打包地址: + Package location: + 封裝位置: - Destination folder : - 目標檔夾: + Destination folder: + 目標資料夾: @@ -926,7 +1537,7 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) 已壓縮的庫封面 (*.clc) @@ -940,17 +1551,17 @@ Some of the comics being added... - 正在添加漫畫... + 正在新增漫畫... Importing comics - 正在導入漫畫 + 正在匯入漫畫 - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderLibrary現在正在創建一個新庫。</p><p>這可能需要幾分鐘時間,您可以先停止該進程,稍後可以通過更新庫選項來更新數據。</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary現在正在建立一個新庫。</p><p>這可能需要幾分鐘時間,您可以先停止該處理程序,稍後可以通過更新庫選項來更新資料。</p> @@ -959,192 +1570,295 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>正在更新當前庫。要獲得更快的更新,請經常更新您的庫。</p><p>您可以停止該進程,稍後繼續更新操作。</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>正在更新目前庫。要獲得更快的更新,請經常更新您的庫。</p><p>您可以停止該處理程序,稍後繼續更新操作。</p> + + + + Upgrading the library + 正在升級庫 + + + + <p>The current library is being upgraded, please wait.</p> + <p>正在升級目前漫畫庫,請稍候。</p> + + + + Scanning the library + 正在掃描庫 + + + + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>正在掃描目前庫的舊版 XML 元資料。</p><p>這只需要執行一次,且僅當該庫由 YACReaderLibrary 9.8.2 或更早版本建立時才需要。</p> + + + + <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> + <p>正在檢查目前漫畫庫中遺失的封面及不完整的漫畫資訊。</p><p>這可能需要幾分鐘。您可以停止此程序,稍後再重新執行。</p> + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 無法建立書庫資料夾:%1。請檢查資料夾存取權限和可用磁碟空間。 + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 書庫資料夾遺失:%1。請先還原該資料夾或重新建立書庫,再進行更新。 + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 書庫資料庫遺失:%1。請先還原備份或重新建立書庫,再進行更新。 + + + + + Unable to start a library database transaction: %1 + 無法啟動書庫資料庫交易:%1 + + + + + Unable to commit the library database: %1 + 無法提交書庫資料庫交易:%1 + + + + LibraryInfoView + + + Library info + 庫資訊 + + + + Number of folders + 資料夾數量 + + + + Number of comics + 漫畫數量 + + + + Number of read comics + 已讀漫畫數量 + + + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + 此書庫忙碌中。請等待維護完成。如果似乎已停滯,請關閉「詳細資訊」中顯示的應用程式,然後重試。 + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + 先前的維護作業可能未釋放此書庫。恢復存取前,請確保沒有任何 YACReaderLibrary 或 YACReaderLibraryServer 作業正在使用它,包括其他電腦上的作業。 + + + + Retry + 重試 - - Upgrading the library - 正在更新庫 + + Recover and retry + 恢復並重試 + + + LibraryMaintenanceLock - - <p>The current library is being upgraded, please wait.</p> - <p>正在更新當前漫畫庫, 請稍候.</p> + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 書庫鎖定的擁有者已變更。請重試作業並檢查新的擁有者,然後再恢復存取。 - - Scanning the library - 正在掃描庫 + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 無法移除維護鎖定:%1。請確保檔案不是唯讀的,且您可以寫入書庫資料夾,然後重試。 - - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>正在掃描當前庫的舊版 XML metadata 資訊。</p><p>這只需要執行一次,且只有當創建庫的 YACReaderLibrary 版本低於 9.8.2 時。</p> + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 維護鎖定檔案路徑被資料夾佔用:%1。請先重新命名或移動該資料夾,然後重試。 - - <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> - <p>正在檢查目前漫畫庫中遺失的封面及不完整的漫畫資訊。</p><p>這可能需要幾分鐘。你可以停止此程序,稍後再重新執行。</p> + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查書庫資料夾的寫入權限。 - - - LibraryInfoView - - Library info - 圖書館資訊 + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 書庫資料夾遺失:%1。請還原該資料夾或重新建立書庫,然後再更新。 - - Number of folders - 資料夾數量 + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 書庫存在維護鎖定,但無法驗證其擁有者(%1)%2 - - Number of comics - 漫畫數量 + + Another maintenance operation is using this library (%1)%2 + 另一個維護作業正在使用此書庫(%1)%2 - - Number of read comics - 已讀漫畫數量 + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查可用磁碟空間和書庫資料夾的存取權限。 LibraryManagementCoordinator - + Error opening the library - 打開庫時出錯 + 開啟庫時出錯 - + Error creating the library - 創建庫時出錯 + 建立庫時出錯 - + Error updating the library 更新庫時出錯 + + + + XML information rescan + 重新掃描 XML 資訊 + + + + Restore recovery + 復原還原作業 + + + + Library upgrade + 書庫升級 + + + + Library maintenance + 書庫維護 + + + + Recover library access + 恢復書庫存取 + LibraryWindow - + YACReader Library - YACReader 庫 + YACReader Library - + Library not available Library ' 庫不可用 + Delete folder - 刪除檔夾 - - - - A repair of this library is already running (%1). Wait for it to finish. - 此庫的修復已在執行中(%1)。請等待其完成。 + 刪除資料夾 - - The library is locked by a repair that did not finish. - 此庫已被一個未完成的修復鎖定。 - - - - The library is locked by a repair started by %1. - 此庫已被 %1 啟動的修復鎖定。 - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - 如果您確定沒有其他修復正在執行,可以移除該鎖定。移除鎖定並繼續? - - - + Upgrade failed - 更新失敗 + 升級失敗 - + There were errors during library upgrade in: - 漫畫庫更新時出現錯誤: + 漫畫庫升級時出現錯誤: - + Restore recovery failed 還原復原失敗 - + Update needed 需要更新 - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - 此庫是使用舊版本的YACReaderLibrary創建的. 它需要更新. 現在更新? + 此庫是使用舊版本的YACReaderLibrary建立的。它需要更新。現在更新? - + Download new version 下載新版本 - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - 此庫是使用較新版本的YACReaderLibrary創建的。 立即下載新版本? + 此庫是使用較新版本的YACReaderLibrary建立的。立即下載新版本? - + Library '%1' is no longer available. Do you want to remove it? - 庫 '%1' 不再可用。 你想刪除它嗎? + 庫「%1」不再可用。您想刪除它嗎? - + Old library 舊的庫 - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - 庫 '%1' 是通過舊版本的YACReaderLibrary創建的。 必須再次創建。 你想現在創建嗎? + 庫「%1」是通過舊版本的 YACReaderLibrary 建立的。必須再次建立。您想現在建立嗎? - + Copying comics... 複製漫畫中... - + Moving comics... 移動漫畫中... Folder name: - 檔夾名稱: + 資料夾名稱: No folder selected - 沒有選中的檔夾 + 沒有選取的資料夾 - Please, select a folder first - 請先選擇一個檔夾 + Please select a folder first + 請先選擇一個資料夾 @@ -1154,231 +1868,265 @@ There was an error accessing the folder's path - 訪問檔夾的路徑時出錯 + 存取資料夾的路徑時發生錯誤 The selected folder and all its contents will be deleted from your disk. Are you sure? - 所選檔夾及其所有內容將從磁片中刪除。 你確定嗎? + 所選資料夾及其所有內容將從磁碟中刪除。您確定嗎? - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - 嘗試刪除所選檔夾時出現問題。 請檢查寫入許可權,並確保沒有其他應用程式在使用這些檔夾或檔。 + 嘗試刪除所選資料夾時出現問題。請檢查寫入權限,並確保沒有其他應用程式在使用這些資料夾或檔案。 - + Save covers - 保存封面 + 儲存封面 - + You are adding too many libraries. - 您添加的庫太多了。 + 您新增的庫太多了。 - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. - 您添加的庫太多了。 +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. + 您新增的庫太多了。 -一般情況只需要一個頂級的庫,您可以使用左側邊欄中的檔夾功能來進行分類管理。 +一般情況只需要一個頂級的庫,您可以使用左側邊欄中的資料夾功能來進行分類管理。 -YACReaderLibrary不會阻止您創建更多的庫,但是您應該保持較低的庫數量來提升性能。 +YACReaderLibrary 不會阻止您建立更多的庫,但是您應該保持較少的庫數量。 - + Library not found 未找到庫 - + The selected folder doesn't contain any library. - 所選檔夾不包含任何庫。 + 所選資料夾不包含任何庫。 - + Are you sure? - 你確定嗎? - - - - Do you want remove - 你想要刪除 - - - - library? - 庫? + 您確定嗎? - Remove and delete metadata - 移除並刪除元數據 + + Do you want to remove the library '%1'? + 您想要刪除「%1」庫? - + Library info - 圖書館資訊 + 庫資訊 - + Assign comics numbers 分配漫畫編號 - - Assign numbers starting in: - 從以下位置開始分配編號: + + Assign numbers starting at: + 起始編號: - + Unable to delete 無法刪除 - + + Edit smart list... + 編輯智慧清單... + + + + Delete smart list + 刪除智慧清單 + + + + + + New folder... + 新增資料夾... + + + + + + Move to folder... + 移至資料夾... + + + + Rename folder... + 重新命名資料夾... + + + + Match to library comics... + 配對書庫中的漫畫... + + + + Track missing issues... + 追蹤缺少期數... + + + + Export reading list... + 匯出閱讀清單... + + + Rename or organize files 重新命名或整理檔案 - + Set the type of the selected comics 設定所選漫畫的類型 - + + Save as smart list… + 儲存為智慧清單… + + + Search filters 搜尋篩選器 - + Unread 未讀 - + In progress 閱讀中 - + Highly rated 高評分 - + Recently added 最近新增 - + Search syntax… 搜尋語法… - + Package operation failed 封裝作業失敗 - + The covers package operation could not be completed. 無法完成封面套件作業。 Rename folder - 重新命名檔夾 + 重新命名資料夾 Invalid folder name - 檔夾名稱無效 + 資料夾名稱無效 The folder name is empty or contains characters that are not supported. - 檔夾名稱為空或包含不支援的字元。 + 資料夾名稱為空或包含不支援的字元。 Unable to rename folder - 無法重新命名檔夾 + 無法重新命名資料夾 A file or folder named '%1' already exists. - 名為「%1」的檔案或檔夾已存在。 + 名為「%1」的檔案或資料夾已存在。 The folder could not be renamed on disk. Please check the folder name and write permissions. Folder: %1 - 無法在磁碟上重新命名該檔夾。請檢查檔夾名稱與寫入權限。 + 無法在磁碟上重新命名該資料夾。請檢查資料夾名稱與寫入權限。 -檔夾:%1 +資料夾:%1 The library database could not be updated. The folder rename on disk was reverted. - 無法更新庫資料庫。磁碟上的檔夾重新命名已復原。 + 無法更新庫資料庫。磁碟上的資料夾重新命名已復原。 The library database could not be updated, and the folder rename on disk could not be reverted. The library now needs to be updated manually. - 無法更新庫資料庫,磁碟上的檔夾重新命名也無法復原。現在需要手動更新該庫。 + 無法更新庫資料庫,磁碟上的資料夾重新命名也無法復原。現在需要手動更新該庫。 - - + + YACReader library database (*.ydb) - YACReader 漫畫庫資料庫 (*.ydb) + YACReader 庫資料庫 (*.ydb) - + The library database backup was created at: %1 漫畫庫資料庫備份已建立於: %1 - + Unable to create the library database backup: %1 無法建立漫畫庫資料庫備份: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? 還原前請關閉 YACReaderLibraryServer 及正在使用此漫畫庫的所有其他 YACReader 應用程式。是否繼續? - + Restoring library database... 正在還原漫畫庫資料庫... - + The current library database is invalid. Restore the selected backup anyway? 目前的漫畫庫資料庫無效。仍要還原所選備份嗎? - - - The library maintenance lock may be stale. Remove it and retry? - 漫畫庫維護鎖可能已失效。是否移除並重試? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1387,71 +2135,77 @@ Restart YACReaderLibrary before attempting recovery again. 再次嘗試復原前,請重新啟動 YACReaderLibrary。 - + The library database was restored successfully. Update the library now? 漫畫庫資料庫已成功還原。是否立即更新漫畫庫? - + Library database damaged 漫畫庫資料庫已損壞 - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. 漫畫庫「%1」的資料庫已損壞,因此無法執行一般更新、維護及備份。YACReader 可以嘗試修復資料庫。部分損壞的資料可能無法復原。現有備份不會被更改。 - + Attempt repair 嘗試修復 - + Restore a backup... 還原備份... - + Repairing library database... 正在修復漫畫庫資料庫... - - + + + Library database repair 修復漫畫庫資料庫 - - Another maintenance operation is currently using this library. Try again after it finishes. - 另一個維護操作正在使用此漫畫庫。請在操作完成後重試。 + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + 是否檢查書庫「%1」的資料庫,並在需要時嘗試修復?現有備份不會變更。 + + + + Check and repair + 檢查並修復 - + The library database is already valid. 漫畫庫資料庫已經有效。 - + Library database repaired 漫畫庫資料庫已修復 - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 已透過重建索引修復漫畫庫資料庫。損壞的原始檔案已保留於: %1 - + Library database rebuilt 漫畫庫資料庫已重建 - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1462,7 +2216,7 @@ Update the library now? 是否立即更新漫畫庫? - + The damaged original was preserved at: @@ -1473,12 +2227,12 @@ The damaged original was preserved at: %1 - + Library database repair failed 漫畫庫資料庫修復失敗 - + The library database could not be repaired: %1%2 @@ -1489,71 +2243,111 @@ You can restore a backup from the Library menu or recreate the library. 您可以從「漫畫庫」選單還原備份,或重新建立漫畫庫。 - + Remove and delete metadata and backups - 移除並刪除中繼資料及備份 + 移除並刪除元資料及備份 - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. - 嘗試刪除所選漫畫時出現問題。 請檢查所選檔或包含檔夾中的寫入許可權。 + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + 嘗試刪除所選漫畫時出現問題。請檢查所選檔案或所在資料夾中的寫入權限。 - + Invalid image 圖片無效 - + The selected file is not a valid image. 所選檔案不是有效影像。 - + + Error saving cover 儲存封面時發生錯誤 - + + There was an error saving the cover image. 儲存封面圖片時發生錯誤。 - + + + Match to library comic + 配對書庫中的漫畫 + + + + This also gives the comic to entries in %n other list(s): %1 + + 這也會將此漫畫關聯到其他 %n 個清單中的項目:%1 + + + + + The comic could not be matched. Nothing was changed. + 無法配對漫畫。未作任何變更。 + + + + The selected missing comics will be removed from this folder. Are you sure? + 選取的缺少漫畫將從此資料夾中移除。確定嗎? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + 選取的漫畫將從磁碟中刪除,選取的缺少漫畫將從此資料夾中移除。確定嗎? + + + Delete comics 刪除漫畫 - + All the selected comics will be deleted from your disk. Are you sure? - 所有選定的漫畫都將從您的磁片中刪除。你確定嗎? + 所有選定的漫畫都將從您的磁碟中刪除。您確定嗎? + + + + Remove the comics from Reading? Their reading progress will be reset. + 是否從「正在閱讀」中移除漫畫?閱讀進度將重設。 + + + + The comics will be removed from Favorites. Are you sure? + 漫畫將從「收藏」中移除。確定嗎? - + Remove comics 移除漫畫 - + Comics will only be deleted from the current label/list. Are you sure? - 漫畫只會從當前標籤/列表中刪除。 你確定嗎? + 漫畫只會從目前標籤/列表中刪除。您確定嗎? - + Library name already exists 庫名已存在 - + There is another library with the name '%1'. - 已存在另一個名為'%1'的庫。 + 已存在另一個名為「%1」的庫。 - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1567,24 +2361,24 @@ Missing files: %3 Create a new library - 創建一個新的庫 + 建立一個新的庫 Open an existing library - 打開現有的庫 + 開啟現有的庫 Export comics info - 導出漫畫資訊 + 匯出漫畫資訊 Import comics info - 導入漫畫資訊 + 匯入漫畫資訊 @@ -1614,7 +2408,7 @@ Missing files: %3 Update current library - 更新當前庫 + 更新目前庫 @@ -1638,433 +2432,504 @@ Missing files: %3 + Repair library database... + 修復書庫資料庫... + + + + Check the current library database and attempt to repair any damage + 檢查目前書庫資料庫並嘗試修復損壞 + + + + Recover library access... + 恢復書庫存取... + + + + Recover access after an interrupted library maintenance operation + 在書庫維護作業中斷後恢復存取 + + + Repair covers and comic info 修復封面及漫畫資訊 - + Retry comics with missing covers or incomplete information 重新處理缺少封面或資訊不完整的漫畫 - + Rename library - 重命名庫 + 重新命名庫 - + Rename current library - 重命名當前庫 + 重新命名目前庫 - + Remove library 移除庫 - + Remove current library from your collection - 從您的集合中移除當前庫 + 從您的集合中移除目前庫 - + Rescan library for XML info 重新掃描庫的 XML 資訊 - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. - 嘗試查找漫畫檔內嵌的 XML 資訊。只有當創建庫的 YACReaderLibrary 版本低於 9.8.2 或者使用第三方軟體嵌入 XML 資訊時,才需要執行該操作。 + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. + 嘗試查找漫畫檔案內嵌的 XML 資訊。只有當該庫由 9.8.2 或更早版本建立,或者您使用第三方軟體將 XML 資訊嵌入檔案時,才需要執行該操作。 - + Open library folder... - 打開庫檔夾... + 開啟庫資料夾... - + Open the root folder of the current library - 打開目前庫的根檔夾 + 開啟目前庫的根資料夾 - + Show library info - 顯示圖書館資訊 + 顯示庫資訊 - + Show information about the current library - 顯示當前庫的信息 + 顯示目前庫的資訊 - + Open current comic - 打開當前漫畫 + 開啟目前漫畫 - - Open current comic on YACReader - 用YACReader打開漫畫 + + Open current comic in YACReader + 用YACReader開啟漫畫 - + Save selected covers to... - 選中的封面保存到... + 將選取的封面儲存到... - + Save covers of the selected comics as JPG files - 保存所選的封面為jpg + 將所選漫畫的封面儲存為 JPG 檔案 - - + + Set as read 設為已讀 - + Set comic as read 漫畫設為已讀 - - + + Set as unread 設為未讀 - + Set comic as unread 漫畫設為未讀 - - + + manga - 漫畫 + 日式漫畫 - + Set issue as manga - 將問題設定為漫畫 + 將此期設定為日式漫畫 - - + + comic 漫畫 - + Set issue as normal - 設置發行狀態為正常發行 + 將此期設定為一般漫畫 - + western manga 西方漫畫 - + Set issue as western manga - 將問題設定為西方漫畫 + 將此期設定為西方漫畫 - - + + web comic - 網路漫畫 + 網絡漫畫 - + Set issue as web comic - 將問題設定為網路漫畫 + 將此期設定為網路漫畫 - - + + yonkoma - 四科馬 + 四格漫畫 - + Set issue as yonkoma - 將問題設定為 yonkoma + 將此期設定為四格漫畫 - + Show/Hide marks 顯示/隱藏標記 - + Show or hide read marks 顯示或隱藏閱讀標記 - + Show/Hide recent indicator 顯示/隱藏最近的指標 - + Show or hide recent indicator 顯示或隱藏最近的指示器 - - + + Fullscreen mode on/off - 全屏模式 開/關 + 全螢幕模式 開/關 - + Help, About YACReader - 幫助, 關於 YACReader + 幫助,關於 YACReader - + Add new folder - 添加新的檔夾 + 新增新的資料夾 - + Add new folder to the current library - 在當前庫下添加新的檔夾 + 在目前庫下新增新的資料夾 - + Rename folder - 重新命名檔夾 + 重新命名資料夾 - + Rename the current folder on disk and in the library - 在磁碟與庫中重新命名目前檔夾 + 在磁碟與庫中重新命名目前資料夾 - + Delete folder - 刪除檔夾 + 刪除資料夾 - + Delete current folder from disk - 從磁片上刪除當前檔夾 + 從磁碟上刪除目前資料夾 - + Select root node 選擇根節點 - + Expand all nodes 展開所有節點 - + Collapse all nodes 折疊所有節點 - + Show options dialog 顯示選項對話框 - + Show comics server options dialog 顯示漫畫伺服器選項對話框 - - + + Change between comics views - 漫畫視圖之間的變化 + 切換漫畫檢視 + + + + Grid view + 網格檢視 - + + Flow view + + + + + Info view + + + + Open folder... - 打開檔夾... + 開啟資料夾... - - + + Rename files... Organize files 重新命名檔案... - - + + Organize into folders... - 整理到檔夾... + 整理到資料夾... - + Set as uncompleted 設為未完成 - + Set as completed 設為已完成 - + Set custom cover 設定自訂封面 - + Delete custom cover 刪除自訂封面 - + western manga (left to right) 西方漫畫(從左到右) - + Open containing folder... - 打開包含檔夾... + 開啟所在資料夾... - Reset comic rating - 重置漫畫評分 + + New reading list + 新增閱讀清單 - + Select all comics 全選漫畫 - + Edit 編輯 - + Assign current order to comics - 將當前序號分配給漫畫 + 將目前序號分配給漫畫 - + Update cover 更新封面 - + Delete selected comics 刪除所選的漫畫 - + Delete metadata from selected comics - 從選定的漫畫中刪除元數據 + 從選定的漫畫中刪除元資料 - + Download tags from Comic Vine 從 Comic Vine 下載標籤 - + Focus search line - 聚焦於搜索行 + 聚焦搜尋列 - + Focus comics view - 聚焦於漫畫視圖 + 聚焦於漫畫檢視 - + Edit shortcuts 編輯快捷鍵 - + &Quit 退出(&Q) - + Update folder - 更新檔夾 + 更新資料夾 - + Update current folder - 更新當前檔夾 + 更新目前資料夾 - + Scan legacy XML metadata - 掃描舊版 XML 元數據 + 掃描舊版 XML 元資料 - - Add new reading list - 添加新的閱讀列表 + + Add a new reading list to the current library + 在目前庫新增新的閱讀列表 - - Add a new reading list to the current library - 在當前庫添加新的閱讀列表 + + New smart list... + 新增智慧清單... + + + + Add a saved search to the current library + 向目前書庫新增已儲存的搜尋 + + + + New folder... + 新增資料夾... + + + + Add a folder for reading lists or smart lists + 新增用於閱讀清單或智慧清單的資料夾 + + + + Import reading lists... + 匯入閱讀清單... + + + + Import CBL reading lists into the current library + 將 CBL 閱讀清單匯入目前書庫 + + + + New + 新增 - + + Add or import reading lists + 新增或匯入閱讀清單 + + + Remove reading list 移除閱讀列表 - + Remove current reading list from the library - 從當前庫移除閱讀列表 + 從目前庫移除閱讀列表 - + Add new label - 添加新標籤 + 新增新標籤 - + Add a new label to this library - 在當前庫添加標籤 + 在目前庫新增標籤 - + Rename selected list - 重命名列表 + 重新命名列表 - + Rename any selected labels or lists - 重命名任何選定的標籤或列表 + 重新命名任何選定的標籤或列表 - + Add to... - 添加到... + 新增到... - + Favorites 收藏夾 - + Add selected comics to favorites list - 將所選漫畫添加到收藏夾列表 + 將所選漫畫新增到收藏夾列表 - - + + Reset rating 重置評分 @@ -2079,7 +2944,7 @@ Missing files: %3 manga - 漫畫 + 日式漫畫 @@ -2089,95 +2954,108 @@ Missing files: %3 web comic - 網路漫畫 + 網絡漫畫 - 4koma (top to botom) + 4koma (top to bottom) 4koma(由上至下) - - - + + + Set type - 套裝類型 + 設定類型 - + Library 庫 - + Folder - 檔夾 + 資料夾 - + Comic 漫畫 - + + Match to library comic... + 配對書庫中的漫畫... + + + + + + + Track missing issues... + 追蹤缺少期數... + + + Open folder... - 打開檔夾... + 開啟資料夾... - + Update folder - 更新檔夾 + 更新資料夾 - + Rename folder - 重新命名檔夾 + 重新命名資料夾 - + Rename files... 重新命名檔案... - + Organize into folders... - 整理到檔夾... + 整理到資料夾... - + Rescan library for XML info 重新掃描庫的 XML 資訊 - + Set as uncompleted 設為未完成 - + Set as completed 設為已完成 - + Set as read 設為已讀 - - + + Set as unread 設為未讀 - + Set custom cover 設定自訂封面 - - + + Delete custom cover 刪除自訂封面 @@ -2200,27 +3078,73 @@ Missing files: %3 最近 1 天 - - Last %1 days - 最近 %1 天 + + Last %1 days + 最近 %1 天 + + + + 1 sublist + 1 個子清單 + + + + %1 sublists + %1 個子清單 + + + + LocalComicListModel + + + file name + 檔案名 + + + + MoveToFolderDialog + + + Move to folder + 移至資料夾 + + + + Top level + 最上層 + + + + Move "%1" to: + 將「%1」移至: + + + + NewListFolderDialog + + + New folder + 新增資料夾 + + + + Reading lists + 閱讀清單 - - 1 sublist - 1 個子清單 + + Smart lists + 智慧清單 - - %1 sublists - %1 個子清單 + + Folder name: + 資料夾名稱: - - - LocalComicListModel - - file name - 檔案名 + + For: + 用於: @@ -2228,22 +3152,22 @@ Missing files: %3 You don't have any libraries yet - 你還沒有庫 + 您還沒有庫 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>您可以在任何檔夾中創建庫,YACReaderLibrary將導入此檔夾中的所有漫畫和文件夾。如果已有庫,則可以打開它們。</p><p>您可以把YACReader當成獨立應用來閱讀電腦上的漫畫。</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>您可以在任何資料夾中建立庫,YACReaderLibrary將匯入此資料夾中的所有漫畫和資料夾。如果已有庫,則可以開啟它們。</p><p>您可以把YACReader當成獨立應用來閱讀電腦上的漫畫。</p> create your first library - 創建你的第一個庫 + 建立您的第一個庫 add an existing one - 添加一個現有庫 + 新增一個現有庫 @@ -2274,43 +3198,43 @@ Missing files: %3 Tray icon settings (experimental) - 託盤圖示設置 (實驗特性) + 系統匣圖示設定(實驗特性) Close to tray - 關閉至託盤 + 關閉至系統匣 - Start into the system tray - 啟動至系統託盤 + Start in the system tray + 啟動至系統匣 Edit Comic Vine API key - 編輯Comic Vine API 密匙 + 編輯 Comic Vine API 密鑰 Comic Vine API key - Comic Vine API 密匙 + Comic Vine API 密鑰 ComicInfo.xml legacy support - ComicInfo.xml 遺留支持 + ComicInfo.xml 遺留支援 Import metadata from ComicInfo.xml when adding new comics Import metada from ComicInfo.xml when adding new comics - 新增漫畫時從 ComicInfo.xml 匯入元數據 + 新增漫畫時從 ComicInfo.xml 匯入元資料 Consider 'recent' items added or updated since X days ago - 考慮自 X 天前新增或更新的「最近」項目 + 將自 X 天前起新增或更新的項目視為「最近」項目 @@ -2320,7 +3244,7 @@ Missing files: %3 Write {comic_file_path} where the path should go in the command - 在命令中應將路徑寫入 {comic_file_path} + 在命令中應出現路徑的位置寫入 {comic_file_path} @@ -2380,7 +3304,7 @@ Missing files: %3 daily - 日常的 + 每日 @@ -2394,10 +3318,10 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2409,12 +3333,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Modifications detection - 修改檢測 + 修改偵測 Compare the modified date of files when updating a library (not recommended) - 更新庫時比較文件的修改日期(不建議) + 更新庫時比較檔案的修改日期(不建議) @@ -2424,12 +3348,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Opacity level - 透明度 + 不透明度 Blur level - 模糊 + 模糊程度 @@ -2438,8 +3362,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Restore defautls - 恢復默認值 + Restore defaults + 恢復預設值 @@ -2490,7 +3414,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Grid view - 網格視圖 + 網格檢視 @@ -2500,7 +3424,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Appearance - 外貌 + 外觀 @@ -2516,12 +3440,93 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFiles - + + + Unknown Series + 未知系列 + + + + series + 系列 + + + + Unknown Publisher + 未知出版社 + + + + publisher + 出版社 + + + + title + 標題 + + + + Unknown Comic + 未知漫畫 + + + + Series #Number - Title + 系列 #編號 - 標題 + + + + Series #Number + 系列 #編號 + + + + Series #Number (of Count) + 系列 #編號(共 總數) + + + + Number - Title + 編號 - 標題 + + + + Series #Number (Year) + 系列 #編號(年份) + + + + Publisher / Series / Series #Number - Title + 出版社 / 系列 / 系列 #編號 - 標題 + + + + Publisher / Imprint / Series / Series #Number - Title + 出版社 / 出版品牌 / 系列 / 系列 #編號 - 標題 + + + + Series / Series #Number + 系列 / 系列 #編號 + + + + Publisher / Series / Series #Number (Year) + 出版社 / 系列 / 系列 #編號(年份) + + + + Series / original file name + 系列 / 原始檔名 + + + Renamed, %1 is already in use 已重新命名,%1 已被使用 - + Missing metadata: %1 缺少中繼資料:%1 @@ -2530,62 +3535,82 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %1 could not be created 無法建立 %1 + + + %1 (copy also failed: %2) + %1(複製也失敗:%2) + + + + the copy did not have the same size as the original + 副本與原件大小不同 + + + + the original could not be deleted after it was copied + 複製後無法刪除原件 + + + + The destination folder could not be created. + 無法建立目標資料夾。 + + + + %1 (the file was left at %2) + %1(檔案留在 %2) + OrganizeFilesCoordinator - - + + Organize files 整理檔案 - + This folder does not contain any comics. - 此檔夾不包含任何漫畫。 + 此資料夾不包含任何漫畫。 - - This library is busy: %1 - 此庫忙碌中:%1 - - - + the library database could not be opened 無法開啟庫資料庫 - + the library database could not be locked for writing 無法鎖定庫資料庫以進行寫入 - + a folder entry could not be restored - 無法還原某個檔夾記錄 + 無法還原某個資料夾記錄 - + a comic entry could not be updated 無法更新某筆漫畫記錄 - + the library database could not be saved: %1 無法儲存庫資料庫:%1 - + the record of the last organize run could not be read 無法讀取上次整理的記錄 - + the folder %1 could not be created - 無法建立檔夾 %1 + 無法建立資料夾 %1 - + %n file(s) could not be moved back 有 %n 個檔案無法移回 @@ -2594,10 +3619,6 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFilesDialog - - Format: - 格式: - Organize files @@ -2667,7 +3688,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t selected folder - 所選檔夾 + 所選資料夾 @@ -2729,19 +3750,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Cancel 取消 - - Copy the list - 複製清單 - Undo 復原 - - Close - 關閉 - Copy failure details @@ -2760,7 +3773,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Save current format as preset... - 將目前格式保存為預設組合... + 將目前格式儲存為預設組合... @@ -2770,17 +3783,17 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Save preset - 保存預設組合 + 儲存預設組合 Preset name: - 預設組合名稱: + 預設組合名稱: A filename format cannot contain "/". Use Organize files to move comics into folders. - 檔名格式不能包含 "/"。請使用「整理檔案」把漫畫移動到檔夾中。 + 檔名格式不能包含「/」。請使用「整理檔案」把漫畫移動到資料夾中。 @@ -2790,12 +3803,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t new folder - 新檔夾 + 新資料夾 This folder does not exist yet. It will be created. - 此檔夾尚不存在,將會被建立。 + 此資料夾尚不存在,將會被建立。 @@ -2878,7 +3891,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %n new folder(s) - %n 個新檔夾 + %n 個新資料夾 @@ -2902,7 +3915,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %n file(s) will be renamed. The folders do not change. You can undo it afterwards. - 將重新命名 %n 個檔案。檔夾不會改變。之後可以復原。 + 將重新命名 %n 個檔案。資料夾不會改變。之後可以復原。 @@ -3043,7 +4056,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %n empty folder(s) were removed. - 已移除 %n 個空檔夾。 + 已移除 %n 個空資料夾。 @@ -3129,291 +4142,400 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Folders - 檔夾 + 資料夾 A filename format cannot contain a slash. Every comic keeps its current folder. Use Organize into folders to move comics. - 檔名格式不能包含斜線。每本漫畫都保留在目前檔夾中。請使用「整理到檔夾」來移動漫畫。 + 檔名格式不能包含斜線。每本漫畫都保留在目前資料夾中。請使用「整理到資料夾」來移動漫畫。 Each part separated by a slash becomes a folder. The last part becomes the file name. The original extension is always kept. - 用斜線分隔的每一部分都會變成一個檔夾。最後一部分是檔名。原有副檔名一律保留。 + 用斜線分隔的每一部分都會變成一個資料夾。最後一部分是檔名。原有副檔名一律保留。 PropertiesDialog - + General info 基本資訊 - + Authors 作者 - + Publishing 出版 - + Plot 情節 - + Notes - 筆記 + 備註 - + Cover page 封面 - + Load previous page as cover 載入上一頁作為封面 - + Load next page as cover 載入下一頁作為封面 - + Reset cover to the default image 將封面重設為預設圖片 - + Load custom cover image 載入自訂封面圖片 - + Series: - 系列: + 系列: - + Title: - 標題: + 標題: + + + + e.g. 1-5, 8, 0.5, Annual 1 + 例如 1-5, 8, 0.5, Annual 1 - - + + of: - 的: + 共: - + + Issue number: - 發行數量: + 期號: - + Volume: - 卷: + 卷: - + Arc number: - 世界線數量: + 故事線編號: - + Story arc: - 故事線: + 故事線: - + alt. number: - 替代。數字: + 替代編號: - + Alternate series: 替代系列: - + Series Group: 系列組: - + Genre: Genere: - 類型: + 題材: - + Size: - 大小: + 大小: - + Writer(s): - 作者: + 編劇: - + Penciller(s): 線稿: - + Inker(s): - 墨稿: + 墨稿: - + Colorist(s): - 上色: + 上色: - + Letterer(s): - 文本: + 嵌字: - + Cover Artist(s): - 封面設計: + 封面設計: - + Editor(s): 編輯: - + Imprint: - 印記: + 出版品牌: - + Day: - 日: + 日: - + Month: - 月: + 月: - + Year: - 年: + 年: - + Publisher: - 出版者: + 出版社: - + Format: - 格式: + 格式: - + Color/BW: - 彩色/黑白: + 彩色/黑白: - + Age rating: - 年齡等級: + 年齡分級: - + Type: 類型: - + Language (ISO): 語言(ISO): - + + + Track missing issues in "%1" + 追蹤「%1」中缺少的期數 + + + + Issues: + 期數: + + + + Track + 追蹤 + + + + At most %1 issues can be tracked at once. + 一次最多可追蹤 %1 期。 + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + 請檢查期號:範圍必須遞增,逗號之間的部分不能為空。 + + + + No new issues to track. + 沒有需要追蹤的新期數。 + + + + %n issue(s) will be tracked: %1. + + 將追蹤 %n 期:%1。 + + + + + That is a lot of issues; check the numbers. + 期數較多;請檢查期號。 + + + + %n issue(s) already in the folder. + + 資料夾中已有 %n 期。 + + + + + %n repeated issue(s). + + %n 期重複。 + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + 未追蹤的期數 + + + + The missing issues could not be tracked. + 無法追蹤缺少的期數。 + + + + Comic skipped + 已略過漫畫 + + + + A comic was removed from the library or now has a different file, so it is skipped. + 某部漫畫已從書庫中移除或檔案已變更,因此將略過。 + + + + + Changes not saved + 變更未儲存 + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + 您正在編輯的某部漫畫已從書庫中移除或檔案已變更。您的變更未儲存。 + + + + The changes could not be saved. + 無法儲存變更。 + + + Invalid cover 封面無效 - + The image is invalid. - 該圖像無效。 + 該影像無效。 - + Synopsis: - 概要: + 簡介: - + Characters: - 角色: + 角色: - + Teams: 團隊: - + Locations: 地點: - + Main character or team: 主要角色或團隊: - + Review: - 審查: + 評論: - + Notes: - 筆記: + 備註: - + Tags: 標籤: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - Comic Vine 連接: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 查看 </a> + Comic Vine 連結:<a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 查看 </a> - + Not found 未找到 - + Comic not found. You should update your library. - 未找到漫畫,請先更新您的庫. + 未找到漫畫,請先更新您的庫。 - + Edit selected comics information - 編輯選中的漫畫資訊 + 編輯選取的漫畫資訊 - + Edit comic information 編輯漫畫資訊 @@ -3423,20 +4545,20 @@ To stop an automatic update tap on the loading indicator next to the Libraries t 7z lib not found - 未找到 7z 庫檔 + 找不到 7z 函式庫 unable to load 7z lib from ./utils - 無法從 ./utils 載入 7z 庫檔 + 無法從 ./utils 載入 7z 函式庫 - + Select custom cover 選擇自訂封面 - + Images (%1) 圖片 (%1) @@ -3458,7 +4580,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Folders - 檔夾 + 資料夾 @@ -3469,43 +4591,176 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists - 添加新的閱讀列表 + 新增新的閱讀列表 - - + + List name: - 列表名稱: + 列表名稱: + + + + Import reading lists + 匯入閱讀清單 + + + + Reading lists (*.cbl *.json);;All files (*) + 閱讀清單 (*.cbl *.json);;所有檔案 (*) - + + New smart list + 新增智慧清單 + + + + + The smart list could not be saved. + 無法儲存智慧清單。 + + + + Edit smart list + 編輯智慧清單 + + + + Delete folder + 刪除資料夾 + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + 是否刪除資料夾「%1」及其中的所有內容?其中的資料夾和清單將被刪除,但漫畫檔案會保留。 + + + Delete list/label - 刪除 列表/標籤 + 刪除列表/標籤 - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - 所選項目將被刪除,您的漫畫或檔夾將不會從您的磁片中刪除。 你確定嗎? + 所選項目將被刪除,您的漫畫或資料夾將不會從您的磁碟中刪除。您確定嗎? - + Rename list name - 重命名列表 + 重新命名列表 + + + + Rename folder + 重新命名資料夾 + + + + + Folder name: + 資料夾名稱: + + + + + + New folder + 新增資料夾 + + + + + The folder could not be created. + 無法建立資料夾。 + + + + Reading list + 閱讀清單 + + + + CBL reading list (*.cbl) + CBL 閱讀清單 (*.cbl) + + + + JSON reading list (*.json) + JSON 閱讀清單 (*.json) + + + + Web page (*.html) + 網頁 (*.html) + + + + + Export reading list + 匯出閱讀清單 + + + + Exporting reading list... + 正在匯出閱讀清單... + + + + The file could not be written. + 無法寫入檔案。 + + + + Move to folder + 移至資料夾 + + + + "%1" could not be moved. + 無法移動「%1」。 + + + + ReadingListPage + + + %n comic(s) + + %n 部漫畫 + + + + + %n missing + + %n 個遺失 + + + + + Missing + 缺少 + + + + Exported from YACReader on %1 + 於 %1 從 YACReader 匯出 RenameLibraryDialog - New Library Name : - 新庫名: + New Library Name: + 新庫名: Rename - 重命名 + 重新命名 @@ -3515,15 +4770,15 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Rename current library - 重命名當前庫 + 重新命名目前庫 ScraperResultsPaginator - Number of volumes found : %1 - 搜索結果: %1 + Number of volumes found: %1 + 找到的卷數:%1 @@ -3533,8 +4788,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of %1 found : %2 - 第 %1 頁 共: %2 條 + Number of %1 found: %2 + 找到的 %1 數量:%2 @@ -3697,7 +4952,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Comic genre - 漫畫類型 + 漫畫題材 @@ -3816,17 +5071,17 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Please provide some additional information for this comic. Please provide some additional information. - 請提供附加資訊. + 請提供附加資訊。 Series: - 系列: + 系列: Use exact match search. Disable if you want to find volumes that match some of the words in the name. - 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的捲,請停用。 + 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的卷,請停用。 @@ -3896,6 +5151,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. 直接開始輸入即可。純文字會搜尋所有元資料。 + + + %1 -> search for `%1` in any database field + %1 -> ??????????? `%1` + 1. Search everywhere @@ -3991,7 +5251,9 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - 提示:空格等同於 AND,搜尋不區分大小寫。使用引號可在一個值內包含空格。 + 提示: +空格等同於 AND,搜尋不區分大小寫。 +使用引號可在一個值內包含空格。 @@ -4026,7 +5288,7 @@ Use quotes to include spaces in a value. Examples show the pattern—replace the values with your own. - 範例會顯示格式;請將值換成你自己的值。 + 範例會顯示格式;請將值換成您自己的值。 @@ -4129,25 +5391,25 @@ Use quotes to include spaces in a value. Please provide some additional information. - 請提供附加資訊. + 請提供附加資訊。 Series: - 系列: + 系列: Use exact match search. Disable if you want to find volumes that match some of the words in the name. - 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的捲,請停用。 + 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的卷,請停用。 SelectComic - Please, select the right comic info. - 請正確選擇漫畫資訊. + Please select the right comic info. + 請正確選擇漫畫資訊。 @@ -4157,12 +5419,12 @@ Use quotes to include spaces in a value. loading cover - 加載封面 + 載入封面 loading description - 加載描述 + 載入描述 @@ -4174,7 +5436,7 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. 請選擇正確的漫畫系列。 @@ -4190,17 +5452,17 @@ Use quotes to include spaces in a value. Nothing found, clear the filter if any. - 未找到任何內容,如果有,請清除過濾器。 + 未找到任何內容,如果有,請清除篩選器。 loading cover - 加載封面 + 載入封面 loading description - 加載描述 + 載入描述 @@ -4213,62 +5475,67 @@ Use quotes to include spaces in a value. Unknown - 未知 + 未知 %1 (%2 unknown) %1 (%n unknown) - + %1(%2 個未知) %1 series - + %1 個系列 - + %1 comics selected %n comic(s) selected - 已選擇 %1 本漫畫 + 已選擇 %1 本漫畫 - + Read - 閱讀 + 已讀 - + In progress - 閱讀中 + 閱讀中 - + Unread - 未讀 + 未讀 - + Total pages - + 總頁數 - + Total size - + 總大小 + + + + Missing + 缺少 - + Series - 系列 + 系列 SeriesQuestion - You are trying to get information for various comics at once, are they part of the same series? - 您正在嘗試同時獲取各種漫畫的資訊,它們是同一系列的嗎? + You are trying to get information for various comics at once. Are they part of the same series? + 您正在嘗試同時獲取多本漫畫的資訊,它們是同一系列的嗎? @@ -4289,22 +5556,6 @@ Use quotes to include spaces in a value. set port 設定連接埠 - - Server connectivity information - 伺服器連接資訊 - - - Scan it! - 掃一掃! - - - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader 適用於 iOS 和 Android 裝置。 <br/>發現它適用於 <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> 或 <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>。 - - - Choose an IP address - 選擇IP地址 - @@ -4319,7 +5570,7 @@ Use quotes to include spaces in a value. Devices on this network can reach your library at the address below. - 此網絡中的裝置可透過以下地址存取你的資料庫。 + 此網絡中的裝置可透過以下地址存取您的庫。 @@ -4329,7 +5580,7 @@ Use quotes to include spaces in a value. Port - 端口 + 連接埠 @@ -4356,38 +5607,69 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader 支援 iOS 及 Android。取得 <a href='https://ios.yacreader.com'>iOS</a> 或 <a href='https://android.yacreader.com'>Android</a> 版本。 + + + SmartListDialog + + + New smart list + 新增智慧清單 + - enable the server - 啟用伺服器 + + Edit smart list + 編輯智慧清單 + + + + Search filters and syntax + 搜尋篩選器與語法 + + + + Name: + 名稱: + + + + Search: + 搜尋: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. 請在左側對漫畫列表進行排序,直到它與漫畫的資訊相符。 - + sort comics to match comic information 排序漫畫以匹配漫畫資訊 - + issues - 發行 + 期 - + remove selected comics 移除所選漫畫 - + restore all removed comics 恢復所有移除的漫畫 + + + Add the %n other issue(s) of this volume as missing comics + + 將此卷的其他 %n 期新增為缺少的漫畫 + + ThemeEditorDialog @@ -4409,7 +5691,7 @@ Use quotes to include spaces in a value. i - 我 + i @@ -4424,7 +5706,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。發布後恢復原樣。 + 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。放開後恢復原樣。 @@ -4434,12 +5716,12 @@ Use quotes to include spaces in a value. Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 @@ -4459,27 +5741,27 @@ Use quotes to include spaces in a value. Theme info - 主題訊息 + 主題資訊 Parameter - 範圍 + 參數 Value - 價值 + 值 Save and apply - 儲存並應用 + 儲存並套用 Export to file... - 匯出到文件... + 匯出到檔案... @@ -4504,7 +5786,7 @@ Use quotes to include spaces in a value. true - 真的 + true @@ -4512,12 +5794,12 @@ Use quotes to include spaces in a value. false - 錯誤的 + false Double-click to toggle - 按兩下切換 + 雙擊切換 @@ -4545,13 +5827,13 @@ Use quotes to include spaces in a value. Save failed - 保存失敗 + 儲存失敗 Could not open file for writing: %1 - 無法開啟文件進行寫入: + 無法開啟檔案進行寫入: %1 @@ -4591,14 +5873,14 @@ Use quotes to include spaces in a value. SEARCH - 搜索 + 搜尋 UpdateLibraryDialog - Updating.... + Updating... 更新中... @@ -4612,6 +5894,14 @@ Use quotes to include spaces in a value. 更新庫 + + ViewSelector + + + View + + + VolumeComicsModel @@ -4630,12 +5920,12 @@ Use quotes to include spaces in a value. issues - 發行 + 期 publisher - 出版者 + 出版社 @@ -4643,7 +5933,7 @@ Use quotes to include spaces in a value. Presets: - 預設: + 預設: @@ -4673,12 +5963,12 @@ Use quotes to include spaces in a value. Show advanced settings - 顯示高級選項 + 顯示進階選項 Custom: - 自定義: + 自訂: @@ -4703,7 +5993,7 @@ Use quotes to include spaces in a value. Zoom - 縮放 + Zoom @@ -4723,12 +6013,12 @@ Use quotes to include spaces in a value. Visibility - 透明度 + 可見度 Light - 亮度 + 光照 @@ -4738,22 +6028,22 @@ Use quotes to include spaces in a value. Low Performance - 低性能 + 低效能 High Performance - 高性能 + 高效能 - Use VSync (improve the image quality in fullscreen mode, worse performance) - 使用VSync (在全屏模式下提高圖像品質, 性能更差) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + 使用 VSync(在全螢幕模式下提高影像品質,但會降低效能) Performance: - 性能: + 效能: @@ -4761,17 +6051,17 @@ Use quotes to include spaces in a value. &Restore - 複位(&R) + 還原(&R) Systray - 系統託盤 + 系統匣 YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary 將繼續在系統託盤中運行. 想要終止程式, 請在系統託盤圖示的上下文菜單中選擇<b>退出</b>. + YACReaderLibrary 將繼續在系統匣中執行。想要終止程式,請在系統匣圖示的快顯功能表中選擇<b>退出</b>。 @@ -4787,34 +6077,74 @@ Use quotes to include spaces in a value. 舊版本 + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 書庫資料庫遺失。請先還原備份或重新建立書庫,再重新掃描 XML 資訊。 + + + + Unable to load the archive backend: %1 + 無法載入壓縮檔後端:%1 + + + + Unable to start an XML rescan transaction. + 無法啟動 XML 重新掃描交易。 + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 選取的資料夾已不在此書庫中。請重新載入書庫後再掃描。 + + + + Unable to read comics for the XML rescan. + 無法讀取用於 XML 重新掃描的漫畫。 + + + + Unable to commit XML metadata changes. + 無法提交 XML 中繼資料變更。 + + + + Unable to update XML metadata for %1. + 無法更新 %1 的 XML 中繼資料。 + + YACReaderFieldEdit - + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 YACReaderFieldPlainTextEdit + + - - - + + + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 @@ -4822,7 +6152,7 @@ Use quotes to include spaces in a value. Save - 保存 + 儲存 @@ -4860,7 +6190,7 @@ Use quotes to include spaces in a value. type to search - 搜索類型 + 輸入以搜尋 diff --git a/YACReaderLibrary/yacreaderlibrary_zh_TW.ts b/YACReaderLibrary/yacreaderlibrary_zh_TW.ts index 2e53e6c9e..78f8adbad 100644 --- a/YACReaderLibrary/yacreaderlibrary_zh_TW.ts +++ b/YACReaderLibrary/yacreaderlibrary_zh_TW.ts @@ -11,15 +11,75 @@ AddLabelDialog + + + red + 紅色 + + + + orange + 橙色 + + + + yellow + 黃色 + + + + green + 綠色 + + + + cyan + 青色 + + + + blue + 藍色 + + + + violet + 紫羅蘭色 + + + + purple + 紫色 + + + + pink + 粉紅色 + + + + white + 白色 + + + + light + 淺色 + + + + dark + 深色 + Label name: - 標籤名稱: + 標籤名稱: Choose a color: - 選擇標籤顏色: + 選擇標籤顏色: @@ -36,19 +96,19 @@ AddLibraryDialog - Comics folder : - 漫畫檔夾: + Comics folder: + 漫畫資料夾: - Library name : + Library name: Library Name : - 庫名: + 庫名: Add - 添加 + 新增 @@ -58,20 +118,20 @@ Add an existing library - 添加一個現有庫 + 新增一個現有庫 ApiKeyDialog - Before you can connect to Comic Vine, you need your own API key. Please, get one free <a href="http://www.comicvine.com/api/">here</a> - 你需要擁有自己的API密鑰才能夠連接Comic Vine. 你可以通過這個鏈接獲得一個免費的<a href="http://www.comicvine.com/api/">API</a>密鑰 + Before you can connect to Comic Vine, you need your own API key. Please get one free <a href="http://www.comicvine.com/api/">here</a> + 您需要擁有自己的 API 金鑰才能夠連線 Comic Vine。您可以通過<a href="http://www.comicvine.com/api/">此連結</a>免費獲得一個 API 金鑰 Paste here your Comic Vine API key - 在此粘貼你的Comic Vine API + 在此貼上您的 Comic Vine API 金鑰 @@ -99,22 +159,22 @@ Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 Custom - 風俗 + 自訂 Remove - 消除 + 移除 @@ -124,22 +184,22 @@ Light: - 光: + 淺色: Dark: - 黑暗的: + 深色: Custom: - 自定義: + 自訂: Import theme... - 導入主題... + 匯入主題... @@ -169,7 +229,7 @@ Import theme - 導入主題 + 匯入主題 @@ -197,107 +257,624 @@ Import failed - 導入失敗 + 匯入失敗 - ClassicComicsView + CblEntriesView - - Hide comic flow - 隱藏 Comic Flow + + Find comic › + 尋找漫畫 › - ComicFilesCoordinator + CblEntryDetail - Copying comics... - 複製漫畫中... + + Series + 系列 - Moving comics... - 移動漫畫中... + + Number + 編號 + + + + Volume + 卷 + + + + Year + 年份 + + + + Format + 格式 + + + + + ComicVine ID + Comic Vine ID + + + + series and number + 系列和編號 + + + + series without the volume, and number + 不含卷號的系列和編號 + + + + letters and digits of the series, and number + 系列中的字母和數字及編號 + + + + series with its year, and number + 含年份的系列和編號 + + + + volume %1 + 第 %1 卷 + + + + Comic in your library + 書庫中的漫畫 + + + + Missing in your library + 書庫中缺少 + + + + Changed by you. + 已由您修改。 + + + + The entry has this comic. + 此項目已關聯此漫畫。 + + + + Matched by %1. + 根據%1配對。 + + + + Your library already has this comic as missing. Matched by %1. + 書庫中已將此漫畫標記為缺少。根據%1配對。 + + + + Possible comic %1 + 可能的漫畫 %1 + + + + + Changed by you: it is imported as missing. + 已由您修改:將作為缺少的漫畫匯入。 + + + + The comics have the same ComicVine ID. + 漫畫的 Comic Vine ID 相同。 + + + + The comic has the same series and number, but its volume or year is different. + 漫畫的系列和編號相同,但卷號或年份不同。 + + + + The comics have the same series and number. + 漫畫的系列和編號相同。 + + + + Values that are different from the file are in amber. + 與檔案不同的值以琥珀色顯示。 + + + + Closest series in your library + 書庫中最相近的系列 + + + + Library + 庫 + + + + Has %1 + 包含 %1 + + + + No comic found. Tried: %1. + 找不到漫畫。已嘗試:%1。 + + + + In the file + 檔案中的資訊 + + + + Entry %1 of %2 + 項目 %1 / %2 + + + + Use this comic + 使用此漫畫 + + + + Use this missing comic + 使用此缺少的漫畫 + + + + CblFindComicPage + + + ‹ All entries + ‹ 所有項目 + + + + Previous problem + 上一個問題 + + + + Next problem + 下一個問題 + + + + Folders + 資料夾 + + + + Search the whole library + 搜尋整個書庫 + + + + File + 檔案 + + + + Series + 系列 + + + + No. + 編號 + + + + Volume + 卷 + + + + Year + 年份 + + + + Go to the next problem after I choose + 選擇後前往下一個問題 + + + + Use this comic + 使用此漫畫 + + + + Cancel + 取消 + + + + Search in this folder + 在此資料夾中搜尋 + + + + Entry %1 of %2 + 項目 %1 / %2 + + + + Missing: no comic found. Browse or search the library. + 缺少:找不到漫畫。請瀏覽或搜尋書庫。 + + + + Ambiguous: %n possible comic(s). Browse or search the library. + + 不明確:有 %n 部可能的漫畫。請瀏覽或搜尋書庫。 + + + + + Browse or search the library to choose another comic. + 請瀏覽或搜尋書庫以選擇其他漫畫。 + + + + Problem %1 of %2 + 問題 %1 / %2 + + + + %n problem(s) + + %n 個問題 + + + + + + + Library + 庫 + + + + Search results + 搜尋結果 + + + + CblImportDialog + + + Match to library comics — %1 + 配對書庫中的漫畫 — %1 + + + + Apply + 套用 + + + + Close + 關閉 + + + + Skip + 略過 + + + + Import + 匯入 + + + + &Name: + 名稱(&N): + + + + A reading list with this name already exists. Import creates another one. + 已存在同名閱讀清單。匯入將建立另一個清單。 + + + + Filter entries + 篩選項目 + + + + Matching the books of %1 with your library... + 正在將 %1 中的書籍與書庫配對... + + + + Matching the entries of %1 with your library... + 正在將 %1 中的項目與書庫配對... + + + + + Match to library comics + 配對書庫中的漫畫 + + + + This also gives comics to entries in %n other list(s): %1 + + 這也會為其他 %n 個清單中的項目關聯漫畫:%1 + + + + + The changes could not be applied. Nothing was changed. + 無法套用變更。未作任何變更。 + + + + The file cannot be opened. + 無法開啟檔案。 + + + + The file is damaged (line %1, column %2). + 檔案已損毀(第 %1 行,第 %2 欄)。 + + + + The file is not a reading list. + 此檔案不是閱讀清單。 + + + + The library cannot be read. + 無法讀取書庫。 + + + + Import reading lists + 匯入閱讀清單 + + + + The reading list could not be imported. Nothing was written. + 無法匯入閱讀清單。未寫入任何內容。 + + + + Import reading lists — %1 (%2 of %3) + 匯入閱讀清單 — %1(%2 / %3) + + + + Import reading lists — %1 + 匯入閱讀清單 — %1 + + + + All (%1) + 全部(%1) + + + + Matched (%1) + 已配對(%1) + + + + Ambiguous (%1) + 不明確(%1) + + + + Missing (%1) + 缺少(%1) + + + + Changed by you (%1) + 已由您修改(%1) + + + + %1 of %n have a comic + + %n 個中有 %1 個已關聯漫畫 + + + + + Find comic... + 尋找漫畫... + + + + Use the original match + 使用原始配對 + + + + Make missing + 標記為缺少 + + + + Import as missing + 作為缺少的漫畫匯入 + + + + CblImportModel + + + Matched + 已配對 + + + + Ambiguous + 不明確 + + + + Missing + 缺少 + + + + Changed + 已修改 + + + + # + # + + + + In the file + 檔案中的資訊 + + + + Comic in your library + 書庫中的漫畫 + + + + Status + 狀態 + + + + Already missing in your library + 書庫中已標記為缺少 + + + + %n possible comic(s) + + %n 部可能的漫畫 + + + + + Not in library + 不在書庫中 + + + + CblMatchComicDialog + + + Match to library comic + 配對書庫中的漫畫 + + + + Loading the library... + 正在載入書庫... + + + + The library cannot be read. + 無法讀取書庫。 + + + + ClassicComicsView + + + Hide comic flow + 隱藏 Comic Flow ComicInfoView - + Characters 角色 - + Main character or team 主要角色或團隊 - + Teams 團隊 - + Locations 地點 - + Authors 作者 - + writer 編劇 - + penciller 鉛筆畫師 - + inker 墨線師 - + colorist 上色師 - + letterer - 字效師 + 嵌字師 - + cover artist 封面畫師 - + editor 編輯 - + imprint 出版品牌 - + Publisher 出版社 - + color 彩色 - + b/w 黑白 @@ -305,28 +882,28 @@ ComicManagementCoordinator - - + + YACReader not found YACReader 未找到 - + YACReader not found. YACReader should be installed in the same folder as YACReaderLibrary. - 未找到YACReader. YACReader應安裝在與YACReaderLibrary相同的檔夾中. + 未找到 YACReader。YACReader 應安裝在與 YACReaderLibrary 相同的資料夾中。 - + YACReader not found. There might be a problem with your YACReader installation. - 未找到YACReader. YACReader的安裝可能有問題. + 未找到 YACReader。YACReader 的安裝可能有問題。 - + Error 錯誤 - + Error opening comic with third party reader. 使用第三方閱讀器開啟漫畫時出錯。 @@ -334,67 +911,67 @@ ComicModel - + yes 是 - + no 否 - + Title 標題 - + File Name 檔案名 - + Pages 頁數 - + Size 大小 - + Read - 閱讀 + 已讀 - + Current Page - 當前頁 + 目前頁 - + Publication Date 發行日期 - + Rating 評分 - + Series 系列 - + Volume - 體積 + 卷 - + Story Arc 故事線 @@ -402,68 +979,109 @@ ComicVineDialog - + skip - 忽略 + 跳過 - + back 返回 - + next 下一步 - + search - 搜索 + 搜尋 - + close 關閉 - - - + + + Looking for volume... - 搜索卷... + 搜尋卷... - - + + comic %1 of %2 - %3 第 %1 本 共 %2 本 - %3 - + %1 comics selected 已選擇 %1 本漫畫 - - Error connecting to ComicVine - ComicVine 連接時出錯 + + Error connecting to Comic Vine + Comic Vine 連線時出錯 + + + + + Retrieving tags for: %1 + 正在檢索標籤:%1 + + + + Creating missing comic %1 of %2 + 正在建立缺少的漫畫 %1 / %2 + + + + Comic Vine did not answer for %n comic(s). + + Comic Vine 未回應 %n 部漫畫的請求。 + + + + + %n missing comic(s) got only the basic data of the volume. + + %n 部缺少的漫畫僅取得卷的基本資訊。 + + + + + %n missing comic(s) have no cover. + + %n 部缺少的漫畫沒有封面。 + + + + + Comic Vine allows about 200 requests per hour; try again later for the comics that were not updated. + Comic Vine 每小時允許約 200 次請求;請稍後重試未更新的漫畫。 + + + + Try again later for the comics that were not updated. + 請稍後重試未更新的漫畫。 - - - Retrieving tags for : %1 - 正在檢索標籤: %1 + + Comic Vine + Comic Vine - + Retrieving volume info... 正在接收卷資訊... - + Looking for comic... - 搜索漫畫中... + 搜尋漫畫中... @@ -478,18 +1096,18 @@ CreateLibraryDialog - Comics folder : - 漫畫檔夾: + Comics folder: + 漫畫資料夾: - Library Name : - 庫名: + Library Name: + 庫名: Create - 創建 + 建立 @@ -498,13 +1116,13 @@ - Create a library could take several minutes. You can stop the process and update the library later for completing the task. - 創建一個新的庫可能需要幾分鐘時間,您可以先停止該進程,稍後可以通過更新庫選項來更新數據。 + Creating a library could take several minutes. You can stop the process and update the library later to complete the task. + 建立一個新的庫可能需要幾分鐘時間,您可以先停止該處理程式,稍後可以通過更新庫選項來更新資料。 Create new library - 創建新的漫畫庫 + 建立新的漫畫庫 @@ -514,15 +1132,23 @@ The selected path does not exist or is not a valid path. Be sure that you have write access to this folder - 所選路徑不存在或不是有效路徑. 確保您具有此檔夾的寫入許可權 + 所選路徑不存在或不是有效路徑。確保您具有此資料夾的寫入權限 DBHelper - + The folder entry could not be found in the library database. - 在庫資料庫中找不到該檔夾的記錄。 + 在庫資料庫中找不到該資料夾的記錄。 + + + + DelayedProgressDialog + + + Cancel + 取消 @@ -530,27 +1156,27 @@ Restore defaults - 恢復默認 + 恢復預設 - To change a shortcut, double click in the key combination and type the new keys. - 更改快捷鍵: 雙擊按鍵組合並輸入新的映射. + To change a shortcut, double-click the key combination and type the new keys. + 更改快速鍵:雙擊按鍵組合並輸入新的映射。 Shortcuts settings - 快捷鍵設置 + 快速鍵設定 Shortcut in use - 快捷鍵被佔用 + 快速鍵被佔用 - The shortcut "%1" is already assigned to other function - 快捷鍵 "%1" 已被映射至其他功能 + The shortcut "%1" is already assigned to another function + 快速鍵「%1」已被映射至其他功能 @@ -582,6 +1208,14 @@ 此標籤尚未包含漫畫 + + EmptyListFolderWidget + + + This folder is empty + 此資料夾是空的 + + EmptyReadingListWidget @@ -601,7 +1235,7 @@ You are not reading anything yet, come on!! - 你還沒有閱讀任何東西,加油!! + 您還沒有閱讀任何東西,加油!! @@ -613,13 +1247,13 @@ ExportComicsInfoDialog - Output file : - 輸出檔: + Output file: + 輸出檔案: Create - 創建 + 建立 @@ -629,7 +1263,7 @@ Export comics info - 導出漫畫資訊 + 匯出漫畫資訊 @@ -644,20 +1278,20 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - 選定的輸出檔路徑不存在或路徑無效. 確保您具有此檔夾的寫入許可權 + 選定的輸出檔案路徑不存在或路徑無效。確保您具有此資料夾的寫入權限 ExportLibraryDialog - Output folder : - 輸出檔夾: + Output folder: + 輸出資料夾: Create - 創建 + 建立 @@ -667,7 +1301,7 @@ Create covers package - 創建封面包 + 建立封面包 @@ -677,43 +1311,36 @@ The selected path for the output file does not exist or is not a valid path. Be sure that you have write access to this folder - 選定的輸出檔路徑不存在或路徑無效. 確保您具有此檔夾的寫入許可權 + 選定的輸出檔案路徑不存在或路徑無效。確保您具有此資料夾的寫入權限 Destination directory - 目標目錄 + 目標資料夾 FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 頁 CRC 校驗失敗: 部分頁面將無法正確顯示 + 第 %1 頁 CRC 校驗失敗:部分頁面將無法正確顯示 - + Unknown error opening the file - 打開檔時出現未知錯誤 + 開啟檔案時出現未知錯誤 - + 7z not found 未找到 7z - - + + Format not supported - 不支持的檔格式 - - - - FolderContentView - - Continue Reading... - 繼續閱讀... + 不支援的檔案格式 @@ -741,7 +1368,7 @@ Read - 閱讀 + 已讀 @@ -779,64 +1406,48 @@ Add new folder - 添加新的檔夾 + 新增新的資料夾 Folder name: - 檔夾名稱: + 資料夾名稱: GridComicsView - + Show info 顯示資訊 - Library - 庫 - - - Folder - 檔夾 - - - Favorites - 收藏夾 - - - Recently added - 最近加入 - - - + Manga 日式漫畫 - + Western manga - 西式漫畫 + 西方漫畫 - + Web comic 網路漫畫 - + Yonkoma 四格漫畫 - + Comic 漫畫 - + Unknown 未知 @@ -869,17 +1480,17 @@ Import comics info - 導入漫畫資訊 + 匯入漫畫資訊 - Info database location : - 資料庫地址: + Info database location: + 資訊資料庫位置: Import - 導入 + 匯入 @@ -889,25 +1500,25 @@ Comics info file (*.ydb) - 漫畫資訊檔(*.ydb) + 漫畫資訊檔案(*.ydb) ImportLibraryDialog - Library Name : - 庫名: + Library Name: + 庫名: - Package location : - 打包地址: + Package location: + 封裝位置: - Destination folder : - 目標檔夾: + Destination folder: + 目標資料夾: @@ -926,7 +1537,7 @@ - Compresed library covers (*.clc) + Compressed library covers (*.clc) 已壓縮的庫封面 (*.clc) @@ -940,17 +1551,17 @@ Some of the comics being added... - 正在添加漫畫... + 正在新增漫畫... Importing comics - 正在導入漫畫 + 正在匯入漫畫 - <p>YACReaderLibrary is now creating a new library.</p><p>Create a library could take several minutes. You can stop the process and update the library later for completing the task.</p> - <p>YACReaderLibrary現在正在創建一個新庫。</p><p>這可能需要幾分鐘時間,您可以先停止該進程,稍後可以通過更新庫選項來更新數據。</p> + <p>YACReaderLibrary is now creating a new library.</p><p>Creating a library could take several minutes. You can stop the process and update the library later to complete the task.</p> + <p>YACReaderLibrary現在正在建立一個新庫。</p><p>這可能需要幾分鐘時間,您可以先停止該處理程式,稍後可以通過更新庫選項來更新資料。</p> @@ -959,18 +1570,18 @@ - <p>The current library is being updated. For faster updates, please, update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> - <p>正在更新當前庫。要獲得更快的更新,請經常更新您的庫。</p><p>您可以停止該進程,稍後繼續更新操作。</p> + <p>The current library is being updated. For faster updates, please update your libraries frequently.</p><p>You can stop the process and continue updating this library later.</p> + <p>正在更新目前庫。要獲得更快的更新,請經常更新您的庫。</p><p>您可以停止該處理程式,稍後繼續更新操作。</p> Upgrading the library - 正在更新庫 + 正在升級庫 <p>The current library is being upgraded, please wait.</p> - <p>正在更新當前漫畫庫, 請稍候.</p> + <p>正在升級目前漫畫庫,請稍候。</p> @@ -979,13 +1590,43 @@ - <p>Current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was crated with YACReaderLibrary 9.8.2 or earlier.</p> - <p>正在掃描當前庫的舊版 XML metadata 資訊。</p><p>這只需要執行一次,且只有當創建庫的 YACReaderLibrary 版本低於 9.8.2 時。</p> + <p>The current library is being scanned for legacy XML metadata information.</p><p>This is only needed once, and only if the library was created with YACReaderLibrary 9.8.2 or earlier.</p> + <p>正在掃描目前庫的舊版 XML 中繼資料。</p><p>這只需要執行一次,且僅當該庫由 YACReaderLibrary 9.8.2 或更早版本建立時才需要。</p> <p>The current library is being checked for missing covers and incomplete comic information.</p><p>This can take several minutes. You can stop the process and run it again later.</p> - <p>正在檢查目前漫畫庫中遺失的封面和不完整的漫畫資訊。</p><p>這可能需要幾分鐘。您可以停止此程序,稍後再重新執行。</p> + <p>正在檢查目前漫畫庫中遺失的封面和不完整的漫畫資訊。</p><p>這可能需要幾分鐘。您可以停止此程式,稍後再重新執行。</p> + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 無法建立書庫資料夾:%1。請檢查資料夾存取權限和可用磁碟空間。 + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 書庫資料夾遺失:%1。請先還原該資料夾或重新建立書庫,再進行更新。 + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 書庫資料庫遺失:%1。請先還原備份或重新建立書庫,再進行更新。 + + + + + Unable to start a library database transaction: %1 + 無法啟動書庫資料庫交易:%1 + + + + + Unable to commit the library database: %1 + 無法提交書庫資料庫交易:%1 @@ -993,7 +1634,7 @@ Library info - 圖書館資訊 + 庫資訊 @@ -1011,140 +1652,213 @@ 已讀漫畫數量 + + LibraryMaintenanceDialog + + + This library is busy. Wait for maintenance to finish. If it appears stuck, close the application shown in Details, then retry. + 此書庫忙碌中。請等待維護完成。如果似乎已停滯,請關閉「詳細資訊」中顯示的應用程式,然後重試。 + + + + An earlier maintenance operation may not have released this library. Before recovering access, make sure no YACReaderLibrary or YACReaderLibraryServer operation is using it, including on another computer. + 先前的維護作業可能未釋放此書庫。恢復存取前,請確保沒有任何 YACReaderLibrary 或 YACReaderLibraryServer 作業正在使用它,包括其他電腦上的作業。 + + + + Retry + 重試 + + + + Recover and retry + 恢復並重試 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 書庫鎖定的擁有者已變更。請重試作業並檢查新的擁有者,然後再恢復存取。 + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 無法移除維護鎖定:%1。請確保檔案不是唯讀的,且您可以寫入書庫資料夾,然後重試。 + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 維護鎖定檔案路徑被資料夾佔用:%1。請先重新命名或移動該資料夾,然後重試。 + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查書庫資料夾的寫入權限。 + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 書庫資料夾遺失:%1。請還原該資料夾或重新建立書庫,然後再更新。 + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 書庫存在維護鎖定,但無法驗證其擁有者(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 另一個維護作業正在使用此書庫(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查可用磁碟空間和書庫資料夾的存取權限。 + + LibraryManagementCoordinator - + Error opening the library - 打開庫時出錯 + 開啟庫時出錯 - + Error creating the library - 創建庫時出錯 + 建立庫時出錯 - + Error updating the library 更新庫時出錯 + + + + XML information rescan + 重新掃描 XML 資訊 + + + + Restore recovery + 復原還原作業 + + + + Library upgrade + 書庫升級 + + + + Library maintenance + 書庫維護 + + + + Recover library access + 恢復書庫存取 + LibraryWindow - + YACReader Library - YACReader 庫 + YACReader Library - + Library not available Library ' 庫不可用 + Delete folder - 刪除檔夾 - - - - A repair of this library is already running (%1). Wait for it to finish. - 此庫的修復已在執行中(%1)。請等待其完成。 - - - - The library is locked by a repair that did not finish. - 此庫已被一個未完成的修復鎖定。 - - - - The library is locked by a repair started by %1. - 此庫已被 %1 啟動的修復鎖定。 - - - - If you are sure that no other repair is running, the lock can be removed. Remove the lock and continue? - 如果您確定沒有其他修復正在執行,可以移除該鎖定。移除鎖定並繼續? + 刪除資料夾 - + Upgrade failed - 更新失敗 + 升級失敗 - + There were errors during library upgrade in: - 漫畫庫更新時出現錯誤: + 漫畫庫升級時出現錯誤: - + Restore recovery failed 還原復原失敗 - + Update needed 需要更新 - + This library was created with a previous version of YACReaderLibrary. It needs to be updated. Update now? - 此庫是使用舊版本的YACReaderLibrary創建的. 它需要更新. 現在更新? + 此庫是使用舊版本的YACReaderLibrary建立的。它需要更新。現在更新? - + Download new version 下載新版本 - + This library was created with a newer version of YACReaderLibrary. Download the new version now? - 此庫是使用較新版本的YACReaderLibrary創建的。 立即下載新版本? + 此庫是使用較新版本的YACReaderLibrary建立的。立即下載新版本? - + Library '%1' is no longer available. Do you want to remove it? - 庫 '%1' 不再可用。 你想刪除它嗎? + 庫「%1」不再可用。您想刪除它嗎? - + Old library 舊的庫 - + Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now? - 庫 '%1' 是通過舊版本的YACReaderLibrary創建的。 必須再次創建。 你想現在創建嗎? + 庫「%1」是通過舊版本的 YACReaderLibrary 建立的。必須再次建立。您想現在建立嗎? - + Copying comics... 複製漫畫中... - + Moving comics... 移動漫畫中... Folder name: - 檔夾名稱: + 資料夾名稱: No folder selected - 沒有選中的檔夾 + 沒有選取的資料夾 - Please, select a folder first - 請先選擇一個檔夾 + Please select a folder first + 請先選擇一個資料夾 @@ -1154,231 +1868,265 @@ There was an error accessing the folder's path - 訪問檔夾的路徑時出錯 + 存取資料夾的路徑時發生錯誤 The selected folder and all its contents will be deleted from your disk. Are you sure? - 所選檔夾及其所有內容將從磁片中刪除。 你確定嗎? + 所選資料夾及其所有內容將從磁碟中刪除。您確定嗎? - There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that no applications are using these folders or any of the contained files. + There was an issue trying to delete the selected folders. Please check for write permissions and be sure that no applications are using these folders or any of the contained files. There was an issue trying to delete the selected folders. Please, check for write permissions and be sure that any applications are using these folders or any of the contained files. - 嘗試刪除所選檔夾時出現問題。 請檢查寫入許可權,並確保沒有其他應用程式在使用這些檔夾或檔。 + 嘗試刪除所選資料夾時出現問題。請檢查寫入權限,並確保沒有其他應用程式在使用這些資料夾或檔案。 - + Save covers - 保存封面 + 儲存封面 - + You are adding too many libraries. - 您添加的庫太多了。 + 您新增的庫太多了。 - + You are adding too many libraries. -You probably only need one library in your top level comics folder, you can browse any subfolders using the folders section in the left sidebar. +You probably only need one library in your top-level comics folder. You can browse any subfolders using the folders section in the left sidebar. -YACReaderLibrary will not stop you from creating more libraries but you should keep the number of libraries low. - 您添加的庫太多了。 +YACReaderLibrary will not stop you from creating more libraries, but you should keep the number of libraries low. + 您新增的庫太多了。 -一般情況只需要一個頂級的庫,您可以使用左側邊欄中的檔夾功能來進行分類管理。 +一般情況只需要一個頂級的庫,您可以使用左側邊欄中的資料夾功能來進行分類管理。 -YACReaderLibrary不會阻止您創建更多的庫,但是您應該保持較低的庫數量來提升性能。 +YACReaderLibrary 不會阻止您建立更多的庫,但是您應該保持較少的庫數量。 - + Library not found 未找到庫 - + The selected folder doesn't contain any library. - 所選檔夾不包含任何庫。 + 所選資料夾不包含任何庫。 - + Are you sure? - 你確定嗎? - - - - Do you want remove - 你想要刪除 - - - - library? - 庫? + 您確定嗎? - Remove and delete metadata - 移除並刪除元數據 + + Do you want to remove the library '%1'? + 您想要刪除「%1」庫? - + Library info - 圖書館資訊 + 庫資訊 - + Assign comics numbers 分配漫畫編號 - - Assign numbers starting in: - 從以下位置開始分配編號: + + Assign numbers starting at: + 起始編號: - + Unable to delete 無法刪除 - + + Edit smart list... + 編輯智慧清單... + + + + Delete smart list + 刪除智慧清單 + + + + + + New folder... + 新增資料夾... + + + + + + Move to folder... + 移至資料夾... + + + + Rename folder... + 重新命名資料夾... + + + + Match to library comics... + 配對書庫中的漫畫... + + + + Track missing issues... + 追蹤缺少期數... + + + + Export reading list... + 匯出閱讀清單... + + + Rename or organize files 重新命名或整理檔案 - + Set the type of the selected comics 設定所選漫畫的類型 - + + Save as smart list… + 儲存為智慧清單… + + + Search filters 搜尋篩選條件 - + Unread 未讀 - + In progress 閱讀中 - + Highly rated 高評分 - + Recently added 最近加入 - + Search syntax… 搜尋語法… - + Package operation failed 封裝作業失敗 - + The covers package operation could not be completed. 無法完成封面套件作業。 Rename folder - 重新命名檔夾 + 重新命名資料夾 Invalid folder name - 檔夾名稱無效 + 資料夾名稱無效 The folder name is empty or contains characters that are not supported. - 檔夾名稱為空或包含不支援的字元。 + 資料夾名稱為空或包含不支援的字元。 Unable to rename folder - 無法重新命名檔夾 + 無法重新命名資料夾 A file or folder named '%1' already exists. - 名為「%1」的檔案或檔夾已存在。 + 名為「%1」的檔案或資料夾已存在。 The folder could not be renamed on disk. Please check the folder name and write permissions. Folder: %1 - 無法在磁碟上重新命名該檔夾。請檢查檔夾名稱與寫入權限。 + 無法在磁碟上重新命名該資料夾。請檢查資料夾名稱與寫入權限。 -檔夾:%1 +資料夾:%1 The library database could not be updated. The folder rename on disk was reverted. - 無法更新庫資料庫。磁碟上的檔夾重新命名已復原。 + 無法更新庫資料庫。磁碟上的資料夾重新命名已復原。 The library database could not be updated, and the folder rename on disk could not be reverted. The library now needs to be updated manually. - 無法更新庫資料庫,磁碟上的檔夾重新命名也無法復原。現在需要手動更新該庫。 + 無法更新庫資料庫,磁碟上的資料夾重新命名也無法復原。現在需要手動更新該庫。 - - + + YACReader library database (*.ydb) - YACReader 漫畫庫資料庫 (*.ydb) + YACReader 庫資料庫 (*.ydb) - + The library database backup was created at: %1 漫畫庫資料庫備份已建立於: %1 - + Unable to create the library database backup: %1 無法建立漫畫庫資料庫備份: %1 - + Close YACReaderLibraryServer and any other YACReader application using this library before restoring. Continue? 還原前請關閉 YACReaderLibraryServer 以及正在使用此漫畫庫的所有其他 YACReader 應用程式。是否繼續? - + Restoring library database... 正在還原漫畫庫資料庫... - + The current library database is invalid. Restore the selected backup anyway? 目前的漫畫庫資料庫無效。仍要還原所選備份嗎? - - - The library maintenance lock may be stale. Remove it and retry? - 漫畫庫維護鎖可能已失效。是否移除並重試? - - - + Restart YACReaderLibrary before attempting recovery again. @@ -1387,71 +2135,77 @@ Restart YACReaderLibrary before attempting recovery again. 再次嘗試復原前,請重新啟動 YACReaderLibrary。 - + The library database was restored successfully. Update the library now? 漫畫庫資料庫已成功還原。是否立即更新漫畫庫? - + Library database damaged 漫畫庫資料庫已損壞 - + The database of library '%1' is damaged, so normal updates, maintenance, and backups are unavailable. YACReader can attempt to repair the database. Some damaged data may not be recoverable. Existing backups will not be changed. 漫畫庫「%1」的資料庫已損壞,因此無法執行一般更新、維護與備份。YACReader 可以嘗試修復資料庫。部分損壞的資料可能無法復原。現有備份不會被變更。 - + Attempt repair 嘗試修復 - + Restore a backup... 還原備份... - + Repairing library database... 正在修復漫畫庫資料庫... - - + + + Library database repair 修復漫畫庫資料庫 - - Another maintenance operation is currently using this library. Try again after it finishes. - 另一個維護操作正在使用此漫畫庫。請在操作完成後重試。 + + Check the database of library '%1' and attempt to repair it if needed? Existing backups will not be changed. + 是否檢查書庫「%1」的資料庫,並在需要時嘗試修復?現有備份不會變更。 - + + Check and repair + 檢查並修復 + + + The library database is already valid. 漫畫庫資料庫已經有效。 - + Library database repaired 漫畫庫資料庫已修復 - + The library database was repaired by rebuilding its indexes. The damaged original was preserved at: %1 已透過重建索引修復漫畫庫資料庫。損壞的原始檔案已保留於: %1 - + Library database rebuilt 漫畫庫資料庫已重建 - + The library database was rebuilt successfully. The damaged original was preserved at: %1 @@ -1462,7 +2216,7 @@ Update the library now? 是否立即更新漫畫庫? - + The damaged original was preserved at: @@ -1473,12 +2227,12 @@ The damaged original was preserved at: %1 - + Library database repair failed 漫畫庫資料庫修復失敗 - + The library database could not be repaired: %1%2 @@ -1489,71 +2243,111 @@ You can restore a backup from the Library menu or recreate the library. 您可以從「漫畫庫」選單還原備份,或重新建立漫畫庫。 - + Remove and delete metadata and backups 移除並刪除中繼資料與備份 - - There was an issue trying to delete the selected comics. Please, check for write permissions in the selected files or containing folder. - 嘗試刪除所選漫畫時出現問題。 請檢查所選檔或包含檔夾中的寫入許可權。 + + There was an issue trying to delete the selected comics. Please check for write permissions in the selected files or containing folder. + 嘗試刪除所選漫畫時出現問題。請檢查所選檔案或所在資料夾中的寫入權限。 - + Invalid image 圖片無效 - + The selected file is not a valid image. 所選檔案不是有效影像。 - + + Error saving cover 儲存封面時發生錯誤 - + + There was an error saving the cover image. 儲存封面圖片時發生錯誤。 - + + + Match to library comic + 配對書庫中的漫畫 + + + + This also gives the comic to entries in %n other list(s): %1 + + 這也會將此漫畫關聯到其他 %n 個清單中的項目:%1 + + + + + The comic could not be matched. Nothing was changed. + 無法配對漫畫。未作任何變更。 + + + + The selected missing comics will be removed from this folder. Are you sure? + 選取的缺少漫畫將從此資料夾中移除。確定嗎? + + + + The selected comics will be deleted from your disk, and the selected missing comics will be removed from this folder. Are you sure? + 選取的漫畫將從磁碟中刪除,選取的缺少漫畫將從此資料夾中移除。確定嗎? + + + Delete comics 刪除漫畫 - + All the selected comics will be deleted from your disk. Are you sure? - 所有選定的漫畫都將從您的磁片中刪除。你確定嗎? + 所有選定的漫畫都將從您的磁碟中刪除。您確定嗎? + + + + Remove the comics from Reading? Their reading progress will be reset. + 是否從「正在閱讀」中移除漫畫?閱讀進度將重設。 + + + + The comics will be removed from Favorites. Are you sure? + 漫畫將從「收藏」中移除。確定嗎? - + Remove comics 移除漫畫 - + Comics will only be deleted from the current label/list. Are you sure? - 漫畫只會從當前標籤/列表中刪除。 你確定嗎? + 漫畫只會從目前標籤/列表中刪除。您確定嗎? - + Library name already exists 庫名已存在 - + There is another library with the name '%1'. - 已存在另一個名為'%1'的庫。 + 已存在另一個名為「%1」的庫。 - + Repaired: %1 Failed: %2 Missing files: %3 @@ -1567,24 +2361,24 @@ Missing files: %3 Create a new library - 創建一個新的庫 + 建立一個新的庫 Open an existing library - 打開現有的庫 + 開啟現有的庫 Export comics info - 導出漫畫資訊 + 匯出漫畫資訊 Import comics info - 導入漫畫資訊 + 匯入漫畫資訊 @@ -1614,7 +2408,7 @@ Missing files: %3 Update current library - 更新當前庫 + 更新目前庫 @@ -1638,433 +2432,504 @@ Missing files: %3 + Repair library database... + 修復書庫資料庫... + + + + Check the current library database and attempt to repair any damage + 檢查目前書庫資料庫並嘗試修復損壞 + + + + Recover library access... + 恢復書庫存取... + + + + Recover access after an interrupted library maintenance operation + 在書庫維護作業中斷後恢復存取 + + + Repair covers and comic info 修復封面與漫畫資訊 - + Retry comics with missing covers or incomplete information 重新處理缺少封面或資訊不完整的漫畫 - + Rename library - 重命名庫 + 重新命名庫 - + Rename current library - 重命名當前庫 + 重新命名目前庫 - + Remove library 移除庫 - + Remove current library from your collection - 從您的集合中移除當前庫 + 從您的集合中移除目前庫 - + Rescan library for XML info 重新掃描庫的 XML 資訊 - - Tries to find XML info embedded in comic files. You only need to do this if the library was created with 9.8.2 or earlier versions or if you are using third party software to embed XML info in the files. - 嘗試查找漫畫檔內嵌的 XML 資訊。只有當創建庫的 YACReaderLibrary 版本低於 9.8.2 或者使用第三方軟體嵌入 XML 資訊時,才需要執行該操作。 + + Tries to find XML info embedded in comic files. You only need to do this if the library was created with version 9.8.2 or earlier, or if you are using third party software to embed XML info in the files. + 嘗試查找漫畫檔案內嵌的 XML 資訊。只有當該庫由 9.8.2 或更早版本建立,或者您使用第三方軟體將 XML 資訊嵌入檔案時,才需要執行該操作。 - + Open library folder... - 開啟資料庫資料夾... + 開啟庫資料夾... - + Open the root folder of the current library - 開啟目前資料庫的根資料夾 + 開啟目前庫的根資料夾 - + Show library info - 顯示圖書館資訊 + 顯示庫資訊 - + Show information about the current library - 顯示當前庫的信息 + 顯示目前庫的資訊 - + Open current comic - 打開當前漫畫 + 開啟目前漫畫 - - Open current comic on YACReader - 用YACReader打開漫畫 + + Open current comic in YACReader + 用YACReader開啟漫畫 - + Save selected covers to... - 選中的封面保存到... + 將選取的封面儲存到... - + Save covers of the selected comics as JPG files - 保存所選的封面為jpg + 將所選漫畫的封面儲存為 JPG 檔案 - - + + Set as read 設為已讀 - + Set comic as read 漫畫設為已讀 - - + + Set as unread 設為未讀 - + Set comic as unread 漫畫設為未讀 - - + + manga - 漫畫 + 日式漫畫 - + Set issue as manga - 將問題設定為漫畫 + 將此期設定為日式漫畫 - - + + comic 漫畫 - + Set issue as normal - 設置發行狀態為正常發行 + 將此期設定為一般漫畫 - + western manga 西方漫畫 - + Set issue as western manga - 將問題設定為西方漫畫 + 將此期設定為西方漫畫 - - + + web comic 網路漫畫 - + Set issue as web comic - 將問題設定為網路漫畫 + 將此期設定為網路漫畫 - - + + yonkoma - 四科馬 + 四格漫畫 - + Set issue as yonkoma - 將問題設定為 yonkoma + 將此期設定為四格漫畫 - + Show/Hide marks 顯示/隱藏標記 - + Show or hide read marks 顯示或隱藏閱讀標記 - + Show/Hide recent indicator 顯示/隱藏最近的指標 - + Show or hide recent indicator 顯示或隱藏最近的指示器 - - + + Fullscreen mode on/off - 全屏模式 開/關 + 全螢幕模式 開/關 - + Help, About YACReader - 幫助, 關於 YACReader + 幫助,關於 YACReader - + Add new folder - 添加新的檔夾 + 新增新的資料夾 - + Add new folder to the current library - 在當前庫下添加新的檔夾 + 在目前庫下新增新的資料夾 - + Rename folder - 重新命名檔夾 + 重新命名資料夾 - + Rename the current folder on disk and in the library - 在磁碟與庫中重新命名目前檔夾 + 在磁碟與庫中重新命名目前資料夾 - + Delete folder - 刪除檔夾 + 刪除資料夾 - + Delete current folder from disk - 從磁片上刪除當前檔夾 + 從磁碟上刪除目前資料夾 - + Select root node 選擇根節點 - + Expand all nodes 展開所有節點 - + Collapse all nodes 折疊所有節點 - + Show options dialog 顯示選項對話框 - + Show comics server options dialog 顯示漫畫伺服器選項對話框 - - + + Change between comics views - 漫畫視圖之間的變化 + 切換漫畫檢視 + + + + Grid view + 網格檢視 + + + + Flow view + - + + Info view + + + + Open folder... - 打開檔夾... + 開啟資料夾... - - + + Rename files... Organize files 重新命名檔案... - - + + Organize into folders... - 整理到檔夾... + 整理到資料夾... - + Set as uncompleted 設為未完成 - + Set as completed 設為已完成 - + Set custom cover 設定自訂封面 - + Delete custom cover 刪除自訂封面 - + western manga (left to right) 西方漫畫(從左到右) - + Open containing folder... - 打開包含檔夾... + 開啟所在資料夾... - Reset comic rating - 重置漫畫評分 + + New reading list + 新增閱讀清單 - + Select all comics 全選漫畫 - + Edit 編輯 - + Assign current order to comics - 將當前序號分配給漫畫 + 將目前序號分配給漫畫 - + Update cover 更新封面 - + Delete selected comics 刪除所選的漫畫 - + Delete metadata from selected comics - 從選定的漫畫中刪除元數據 + 從選定的漫畫中刪除中繼資料 - + Download tags from Comic Vine 從 Comic Vine 下載標籤 - + Focus search line - 聚焦於搜索行 + 聚焦搜尋列 - + Focus comics view - 聚焦於漫畫視圖 + 聚焦於漫畫檢視 - + Edit shortcuts - 編輯快捷鍵 + 編輯快速鍵 - + &Quit 退出(&Q) - + Update folder - 更新檔夾 + 更新資料夾 - + Update current folder - 更新當前檔夾 + 更新目前資料夾 - + Scan legacy XML metadata - 掃描舊版 XML 元數據 + 掃描舊版 XML 元資料 - - Add new reading list - 添加新的閱讀列表 + + Add a new reading list to the current library + 在目前庫新增新的閱讀列表 - - Add a new reading list to the current library - 在當前庫添加新的閱讀列表 + + New smart list... + 新增智慧清單... + + + + Add a saved search to the current library + 向目前書庫新增已儲存的搜尋 + + + + New folder... + 新增資料夾... + + + + Add a folder for reading lists or smart lists + 新增用於閱讀清單或智慧清單的資料夾 + + + + Import reading lists... + 匯入閱讀清單... + + + + Import CBL reading lists into the current library + 將 CBL 閱讀清單匯入目前書庫 + + + + New + 新增 + + + + Add or import reading lists + 新增或匯入閱讀清單 - + Remove reading list 移除閱讀列表 - + Remove current reading list from the library - 從當前庫移除閱讀列表 + 從目前庫移除閱讀列表 - + Add new label - 添加新標籤 + 新增新標籤 - + Add a new label to this library - 在當前庫添加標籤 + 在目前庫新增標籤 - + Rename selected list - 重命名列表 + 重新命名列表 - + Rename any selected labels or lists - 重命名任何選定的標籤或列表 + 重新命名任何選定的標籤或列表 - + Add to... - 添加到... + 新增到... - + Favorites 收藏夾 - + Add selected comics to favorites list - 將所選漫畫添加到收藏夾列表 + 將所選漫畫新增到收藏夾列表 - - + + Reset rating 重置評分 @@ -2079,7 +2944,7 @@ Missing files: %3 manga - 漫畫 + 日式漫畫 @@ -2093,91 +2958,104 @@ Missing files: %3 - 4koma (top to botom) + 4koma (top to bottom) 4koma(由上至下) - - - + + + Set type - 套裝類型 + 設定類型 - + Library 庫 - + Folder - 檔夾 + 資料夾 - + Comic 漫畫 - + + Match to library comic... + 配對書庫中的漫畫... + + + + + + + Track missing issues... + 追蹤缺少期數... + + + Open folder... - 打開檔夾... + 開啟資料夾... - + Update folder - 更新檔夾 + 更新資料夾 - + Rename folder - 重新命名檔夾 + 重新命名資料夾 - + Rename files... 重新命名檔案... - + Organize into folders... - 整理到檔夾... + 整理到資料夾... - + Rescan library for XML info 重新掃描庫的 XML 資訊 - + Set as uncompleted 設為未完成 - + Set as completed 設為已完成 - + Set as read 設為已讀 - - + + Set as unread 設為未讀 - + Set custom cover 設定自訂封面 - - + + Delete custom cover 刪除自訂封面 @@ -2200,27 +3078,73 @@ Missing files: %3 最近 1 天 - - Last %1 days - 最近 %1 天 + + Last %1 days + 最近 %1 天 + + + + 1 sublist + 1 個子清單 + + + + %1 sublists + %1 個子清單 + + + + LocalComicListModel + + + file name + 檔案名 + + + + MoveToFolderDialog + + + Move to folder + 移至資料夾 + + + + Top level + 最上層 + + + + Move "%1" to: + 將「%1」移至: + + + + NewListFolderDialog + + + New folder + 新增資料夾 + + + + Reading lists + 閱讀清單 - - 1 sublist - 1 個子清單 + + Smart lists + 智慧清單 - - %1 sublists - %1 個子清單 + + Folder name: + 資料夾名稱: - - - LocalComicListModel - - file name - 檔案名 + + For: + 用於: @@ -2228,22 +3152,22 @@ Missing files: %3 You don't have any libraries yet - 你還沒有庫 + 您還沒有庫 - <p>You can create a library in any folder, YACReaderLibrary will import all comics and folders from this folder. If you have created any library in the past you can open them.</p><p>Don't forget that you can use YACReader as a stand alone application for reading the comics on your computer.</p> - <p>您可以在任何檔夾中創建庫,YACReaderLibrary將導入此檔夾中的所有漫畫和文件夾。如果已有庫,則可以打開它們。</p><p>您可以把YACReader當成獨立應用來閱讀電腦上的漫畫。</p> + <p>You can create a library in any folder. YACReaderLibrary will import all comics and folders from this folder. If you have created any libraries in the past, you can open them.</p><p>Don't forget that you can use YACReader as a standalone application for reading the comics on your computer.</p> + <p>您可以在任何資料夾中建立庫,YACReaderLibrary將匯入此資料夾中的所有漫畫和資料夾。如果已有庫,則可以開啟它們。</p><p>您可以把YACReader當成獨立應用來閱讀電腦上的漫畫。</p> create your first library - 創建你的第一個庫 + 建立您的第一個庫 add an existing one - 添加一個現有庫 + 新增一個現有庫 @@ -2274,43 +3198,43 @@ Missing files: %3 Tray icon settings (experimental) - 託盤圖示設置 (實驗特性) + 系統匣圖示設定(實驗特性) Close to tray - 關閉至託盤 + 關閉至系統匣 - Start into the system tray - 啟動至系統託盤 + Start in the system tray + 啟動至系統匣 Edit Comic Vine API key - 編輯Comic Vine API 密匙 + 編輯 Comic Vine API 金鑰 Comic Vine API key - Comic Vine API 密匙 + Comic Vine API 密鑰 ComicInfo.xml legacy support - ComicInfo.xml 遺留支持 + ComicInfo.xml 遺留支援 Import metadata from ComicInfo.xml when adding new comics Import metada from ComicInfo.xml when adding new comics - 新增漫畫時從 ComicInfo.xml 匯入元數據 + 新增漫畫時從 ComicInfo.xml 匯入元資料 Consider 'recent' items added or updated since X days ago - 考慮自 X 天前新增或更新的「最近」項目 + 將自 X 天前起新增或更新的項目視為「最近」項目 @@ -2320,7 +3244,7 @@ Missing files: %3 Write {comic_file_path} where the path should go in the command - 在命令中應將路徑寫入 {comic_file_path} + 在命令中應出現路徑的位置寫入 {comic_file_path} @@ -2380,7 +3304,7 @@ Missing files: %3 daily - 日常的 + 每日 @@ -2394,10 +3318,10 @@ Missing files: %3 - WARNING! During library updates writes to the database are disabled! -Don't schedule updates while you may be using the app actively. -During automatic updates the app will block some of the actions until the update is finished. -To stop an automatic update tap on the loading indicator next to the Libraries title. + WARNING! During library updates, writes to the database are disabled! +Don't schedule updates while you may be actively using the app. +During automatic updates, the app will block some of the actions until the update is finished. +To stop an automatic update, tap on the loading indicator next to the Libraries title. WARNING! During library updates writes to the database are disabled! Don't schedule updates while you may be using the app actively. To stop an automatic update tap on the loading indicator next to the Libraries title. @@ -2409,12 +3333,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Modifications detection - 修改檢測 + 修改偵測 Compare the modified date of files when updating a library (not recommended) - 更新庫時比較文件的修改日期(不建議) + 更新庫時比較檔案的修改日期(不建議) @@ -2424,12 +3348,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Opacity level - 透明度 + 不透明度 Blur level - 模糊 + 模糊程度 @@ -2438,8 +3362,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Restore defautls - 恢復默認值 + Restore defaults + 恢復預設值 @@ -2490,7 +3414,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Grid view - 網格視圖 + 網格檢視 @@ -2500,7 +3424,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Appearance - 外貌 + 外觀 @@ -2516,12 +3440,93 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFiles - + + + Unknown Series + 未知系列 + + + + series + 系列 + + + + Unknown Publisher + 未知出版社 + + + + publisher + 出版社 + + + + title + 標題 + + + + Unknown Comic + 未知漫畫 + + + + Series #Number - Title + 系列 #編號 - 標題 + + + + Series #Number + 系列 #編號 + + + + Series #Number (of Count) + 系列 #編號(共 總數) + + + + Number - Title + 編號 - 標題 + + + + Series #Number (Year) + 系列 #編號(年份) + + + + Publisher / Series / Series #Number - Title + 出版社 / 系列 / 系列 #編號 - 標題 + + + + Publisher / Imprint / Series / Series #Number - Title + 出版社 / 出版品牌 / 系列 / 系列 #編號 - 標題 + + + + Series / Series #Number + 系列 / 系列 #編號 + + + + Publisher / Series / Series #Number (Year) + 出版社 / 系列 / 系列 #編號(年份) + + + + Series / original file name + 系列 / 原始檔名 + + + Renamed, %1 is already in use 已重新命名,%1 已被使用 - + Missing metadata: %1 缺少中繼資料:%1 @@ -2530,62 +3535,82 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %1 could not be created 無法建立 %1 + + + %1 (copy also failed: %2) + %1(複製也失敗:%2) + + + + the copy did not have the same size as the original + 副本與原件大小不同 + + + + the original could not be deleted after it was copied + 複製後無法刪除原件 + + + + The destination folder could not be created. + 無法建立目標資料夾。 + + + + %1 (the file was left at %2) + %1(檔案留在 %2) + OrganizeFilesCoordinator - - + + Organize files 整理檔案 - + This folder does not contain any comics. - 此檔夾不包含任何漫畫。 + 此資料夾不包含任何漫畫。 - - This library is busy: %1 - 此庫忙碌中:%1 - - - + the library database could not be opened 無法開啟庫資料庫 - + the library database could not be locked for writing 無法鎖定庫資料庫以進行寫入 - + a folder entry could not be restored - 無法還原某個檔夾記錄 + 無法還原某個資料夾記錄 - + a comic entry could not be updated 無法更新某筆漫畫記錄 - + the library database could not be saved: %1 無法儲存庫資料庫:%1 - + the record of the last organize run could not be read 無法讀取上次整理的記錄 - + the folder %1 could not be created - 無法建立檔夾 %1 + 無法建立資料夾 %1 - + %n file(s) could not be moved back 有 %n 個檔案無法移回 @@ -2594,10 +3619,6 @@ To stop an automatic update tap on the loading indicator next to the Libraries t OrganizeFilesDialog - - Format: - 格式: - Organize files @@ -2667,7 +3688,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t selected folder - 所選檔夾 + 所選資料夾 @@ -2729,19 +3750,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Cancel 取消 - - Copy the list - 複製清單 - Undo 復原 - - Close - 關閉 - Copy failure details @@ -2760,7 +3773,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Save current format as preset... - 將目前格式保存為預設組合... + 將目前格式儲存為預設組合... @@ -2770,17 +3783,17 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Save preset - 保存預設組合 + 儲存預設組合 Preset name: - 預設組合名稱: + 預設組合名稱: A filename format cannot contain "/". Use Organize files to move comics into folders. - 檔名格式不能包含 "/"。請使用「整理檔案」把漫畫移動到檔夾中。 + 檔名格式不能包含「/」。請使用「整理檔案」把漫畫移動到資料夾中。 @@ -2790,12 +3803,12 @@ To stop an automatic update tap on the loading indicator next to the Libraries t new folder - 新檔夾 + 新資料夾 This folder does not exist yet. It will be created. - 此檔夾尚不存在,將會被建立。 + 此資料夾尚不存在,將會被建立。 @@ -2878,7 +3891,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %n new folder(s) - %n 個新檔夾 + %n 個新資料夾 @@ -2902,7 +3915,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %n file(s) will be renamed. The folders do not change. You can undo it afterwards. - 將重新命名 %n 個檔案。檔夾不會改變。之後可以復原。 + 將重新命名 %n 個檔案。資料夾不會改變。之後可以復原。 @@ -3043,7 +4056,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t %n empty folder(s) were removed. - 已移除 %n 個空檔夾。 + 已移除 %n 個空資料夾。 @@ -3129,291 +4142,400 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Folders - 檔夾 + 資料夾 A filename format cannot contain a slash. Every comic keeps its current folder. Use Organize into folders to move comics. - 檔名格式不能包含斜線。每本漫畫都保留在目前檔夾中。請使用「整理到檔夾」來移動漫畫。 + 檔名格式不能包含斜線。每本漫畫都保留在目前資料夾中。請使用「整理到資料夾」來移動漫畫。 Each part separated by a slash becomes a folder. The last part becomes the file name. The original extension is always kept. - 用斜線分隔的每一部分都會變成一個檔夾。最後一部分是檔名。原有副檔名一律保留。 + 用斜線分隔的每一部分都會變成一個資料夾。最後一部分是檔名。原有副檔名一律保留。 PropertiesDialog - + General info 基本資訊 - + Authors 作者 - + Publishing 出版 - + Plot 情節 - + Notes - 筆記 + 備註 - + Cover page 封面 - + Load previous page as cover 載入上一頁作為封面 - + Load next page as cover 載入下一頁作為封面 - + Reset cover to the default image 將封面重設為預設圖片 - + Load custom cover image 載入自訂封面圖片 - + Series: - 系列: + 系列: - + Title: - 標題: + 標題: + + + + e.g. 1-5, 8, 0.5, Annual 1 + 例如 1-5, 8, 0.5, Annual 1 - - + + of: - 的: + 共: - + + Issue number: - 發行數量: + 期號: - + Volume: - 卷: + 卷: - + Arc number: - 世界線數量: + 故事線編號: - + Story arc: - 故事線: + 故事線: - + alt. number: - 替代。數字: + 替代編號: - + Alternate series: 替代系列: - + Series Group: 系列組: - + Genre: Genere: - 類型: + 題材: - + Size: - 大小: + 大小: - + Writer(s): - 作者: + 編劇: - + Penciller(s): 線稿: - + Inker(s): - 墨稿: + 墨稿: - + Colorist(s): - 上色: + 上色: - + Letterer(s): - 文本: + 嵌字: - + Cover Artist(s): - 封面設計: + 封面設計: - + Editor(s): 編輯: - + Imprint: - 印記: + 出版品牌: - + Day: - 日: + 日: - + Month: - 月: + 月: - + Year: - 年: + 年: - + Publisher: - 出版者: + 出版社: - + Format: - 格式: + 格式: - + Color/BW: - 彩色/黑白: + 彩色/黑白: - + Age rating: - 年齡等級: + 年齡分級: - + Type: 類型: - + Language (ISO): 語言(ISO): - + + + Track missing issues in "%1" + 追蹤「%1」中缺少的期數 + + + + Issues: + 期數: + + + + Track + 追蹤 + + + + At most %1 issues can be tracked at once. + 一次最多可追蹤 %1 期。 + + + + Check the issues: a range must go up, and a part between commas cannot be empty. + 請檢查期號:範圍必須遞增,逗號之間的部分不能為空。 + + + + No new issues to track. + 沒有需要追蹤的新期數。 + + + + %n issue(s) will be tracked: %1. + + 將追蹤 %n 期:%1。 + + + + + That is a lot of issues; check the numbers. + 期數較多;請檢查期號。 + + + + %n issue(s) already in the folder. + + 資料夾中已有 %n 期。 + + + + + %n repeated issue(s). + + %n 期重複。 + + + + + Set a series, a volume, or a title. + + + + + Set a series or a volume. + + + + + Issues not tracked + 未追蹤的期數 + + + + The missing issues could not be tracked. + 無法追蹤缺少的期數。 + + + + Comic skipped + 已略過漫畫 + + + + A comic was removed from the library or now has a different file, so it is skipped. + 某部漫畫已從書庫中移除或檔案已變更,因此將略過。 + + + + + Changes not saved + 變更未儲存 + + + + A comic that you are editing was removed from the library or now has a different file. Your changes were not saved. + 您正在編輯的某部漫畫已從書庫中移除或檔案已變更。您的變更未儲存。 + + + + The changes could not be saved. + 無法儲存變更。 + + + Invalid cover 封面無效 - + The image is invalid. - 該圖像無效。 + 該影像無效。 - + Synopsis: - 概要: + 簡介: - + Characters: - 角色: + 角色: - + Teams: 團隊: - + Locations: 地點: - + Main character or team: 主要角色或團隊: - + Review: - 審查: + 評論: - + Notes: - 筆記: + 備註: - + Tags: 標籤: - + Comic Vine link: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> view </a> - Comic Vine 連接: <a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 查看 </a> + Comic Vine 連結:<a style='color: #FFCB00; text-decoration:none; font-weight:bold;' href="http://www.comicvine.com/comic/4000-%1/"> 查看 </a> - + Not found 未找到 - + Comic not found. You should update your library. - 未找到漫畫,請先更新您的庫. + 未找到漫畫,請先更新您的庫。 - + Edit selected comics information - 編輯選中的漫畫資訊 + 編輯選取的漫畫資訊 - + Edit comic information 編輯漫畫資訊 @@ -3423,20 +4545,20 @@ To stop an automatic update tap on the loading indicator next to the Libraries t 7z lib not found - 未找到 7z 庫檔 + 找不到 7z 函式庫 unable to load 7z lib from ./utils - 無法從 ./utils 載入 7z 庫檔 + 無法從 ./utils 載入 7z 函式庫 - + Select custom cover 選擇自訂封面 - + Images (%1) 圖片 (%1) @@ -3458,7 +4580,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Folders - 檔夾 + 資料夾 @@ -3469,43 +4591,176 @@ To stop an automatic update tap on the loading indicator next to the Libraries t ReadingListManagementCoordinator - + Add new reading lists - 添加新的閱讀列表 + 新增新的閱讀列表 - - + + List name: - 列表名稱: + 列表名稱: + + + + Import reading lists + 匯入閱讀清單 + + + + Reading lists (*.cbl *.json);;All files (*) + 閱讀清單 (*.cbl *.json);;所有檔案 (*) - + + New smart list + 新增智慧清單 + + + + + The smart list could not be saved. + 無法儲存智慧清單。 + + + + Edit smart list + 編輯智慧清單 + + + + Delete folder + 刪除資料夾 + + + + Delete the folder "%1" and everything in it? Its folders and lists are deleted; your comic files are not. + 是否刪除資料夾「%1」及其中的所有內容?其中的資料夾和清單將被刪除,但漫畫檔案會保留。 + + + Delete list/label - 刪除 列表/標籤 + 刪除列表/標籤 - + The selected item will be deleted, your comics or folders will NOT be deleted from your disk. Are you sure? - 所選項目將被刪除,您的漫畫或檔夾將不會從您的磁片中刪除。 你確定嗎? + 所選項目將被刪除,您的漫畫或資料夾將不會從您的磁碟中刪除。您確定嗎? - + Rename list name - 重命名列表 + 重新命名列表 + + + + Rename folder + 重新命名資料夾 + + + + + Folder name: + 資料夾名稱: + + + + + + New folder + 新增資料夾 + + + + + The folder could not be created. + 無法建立資料夾。 + + + + Reading list + 閱讀清單 + + + + CBL reading list (*.cbl) + CBL 閱讀清單 (*.cbl) + + + + JSON reading list (*.json) + JSON 閱讀清單 (*.json) + + + + Web page (*.html) + 網頁 (*.html) + + + + + Export reading list + 匯出閱讀清單 + + + + Exporting reading list... + 正在匯出閱讀清單... + + + + The file could not be written. + 無法寫入檔案。 + + + + Move to folder + 移至資料夾 + + + + "%1" could not be moved. + 無法移動「%1」。 + + + + ReadingListPage + + + %n comic(s) + + %n 部漫畫 + + + + + %n missing + + %n 個遺失 + + + + + Missing + 缺少 + + + + Exported from YACReader on %1 + 於 %1 從 YACReader 匯出 RenameLibraryDialog - New Library Name : - 新庫名: + New Library Name: + 新庫名: Rename - 重命名 + 重新命名 @@ -3515,15 +4770,15 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Rename current library - 重命名當前庫 + 重新命名目前庫 ScraperResultsPaginator - Number of volumes found : %1 - 搜索結果: %1 + Number of volumes found: %1 + 找到的卷數:%1 @@ -3533,8 +4788,8 @@ To stop an automatic update tap on the loading indicator next to the Libraries t - Number of %1 found : %2 - 第 %1 頁 共: %2 條 + Number of %1 found: %2 + 找到的 %1 數量:%2 @@ -3697,7 +4952,7 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Comic genre - 漫畫類型 + 漫畫題材 @@ -3816,17 +5071,17 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Please provide some additional information for this comic. Please provide some additional information. - 請提供附加資訊. + 請提供附加資訊。 Series: - 系列: + 系列: Use exact match search. Disable if you want to find volumes that match some of the words in the name. - 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的捲,請停用。 + 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的卷,請停用。 @@ -3896,6 +5151,11 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Just start typing. Plain text search across all metadata. 直接開始輸入即可。純文字會搜尋所有中繼資料。 + + + %1 -> search for `%1` in any database field + %1 -> ??????????? `%1` + 1. Search everywhere @@ -3991,7 +5251,9 @@ To stop an automatic update tap on the loading indicator next to the Libraries t Tips: Spaces act like AND, and searches are not case-sensitive. Use quotes to include spaces in a value. - 提示:空格等同於 AND,搜尋不區分大小寫。使用引號可在一個值中包含空格。 + 提示: +空格等同於 AND,搜尋不區分大小寫。 +使用引號可在一個值中包含空格。 @@ -4129,25 +5391,25 @@ Use quotes to include spaces in a value. Please provide some additional information. - 請提供附加資訊. + 請提供附加資訊。 Series: - 系列: + 系列: Use exact match search. Disable if you want to find volumes that match some of the words in the name. - 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的捲,請停用。 + 使用精確匹配搜尋。如果您想要尋找與名稱中某些單字相符的卷,請停用。 SelectComic - Please, select the right comic info. - 請正確選擇漫畫資訊. + Please select the right comic info. + 請正確選擇漫畫資訊。 @@ -4157,12 +5419,12 @@ Use quotes to include spaces in a value. loading cover - 加載封面 + 載入封面 loading description - 加載描述 + 載入描述 @@ -4174,7 +5436,7 @@ Use quotes to include spaces in a value. SelectVolume - Please, select the right series for your comic. + Please select the right series for your comic. 請選擇正確的漫畫系列。 @@ -4190,17 +5452,17 @@ Use quotes to include spaces in a value. Nothing found, clear the filter if any. - 未找到任何內容,如果有,請清除過濾器。 + 未找到任何內容,如果有,請清除篩選條件。 loading cover - 加載封面 + 載入封面 loading description - 加載描述 + 載入描述 @@ -4213,62 +5475,67 @@ Use quotes to include spaces in a value. Unknown - 未知 + 未知 %1 (%2 unknown) %1 (%n unknown) - + %1(%2 個未知) %1 series - + %1 個系列 - + %1 comics selected %n comic(s) selected - 已選擇 %1 本漫畫 + 已選擇 %1 本漫畫 - + Read - 閱讀 + 已讀 - + In progress - 閱讀中 + 閱讀中 - + Unread - 未讀 + 未讀 - + Total pages - + 總頁數 - + Total size - + 總大小 + + + + Missing + 缺少 - + Series - 系列 + 系列 SeriesQuestion - You are trying to get information for various comics at once, are they part of the same series? - 您正在嘗試同時獲取各種漫畫的資訊,它們是同一系列的嗎? + You are trying to get information for various comics at once. Are they part of the same series? + 您正在嘗試同時獲取多本漫畫的資訊,它們是同一系列的嗎? @@ -4289,22 +5556,6 @@ Use quotes to include spaces in a value. set port 設定連接埠 - - Server connectivity information - 伺服器連接資訊 - - - Scan it! - 掃一掃! - - - YACReader is available for iOS and Android devices.<br/>Discover it for <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> or <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>. - YACReader 適用於 iOS 和 Android 裝置。 <br/>發現它適用於 <a href='https://ios.yacreader.com' style='color:rgb(193, 148, 65)'>iOS</a> 或 <a href='https://android.yacreader.com' style='color:rgb(193, 148, 65)'>Android</a>。 - - - Choose an IP address - 選擇IP地址 - @@ -4319,7 +5570,7 @@ Use quotes to include spaces in a value. Devices on this network can reach your library at the address below. - 此網路中的裝置可透過以下位址存取您的資料庫。 + 此網路中的裝置可透過以下位址存取您的庫。 @@ -4329,7 +5580,7 @@ Use quotes to include spaces in a value. Port - 端口 + 連接埠 @@ -4356,38 +5607,69 @@ Use quotes to include spaces in a value. YACReader is available for iOS and Android. Discover it for <a href='https://ios.yacreader.com'>iOS</a> or <a href='https://android.yacreader.com'>Android</a>. YACReader 支援 iOS 與 Android。取得 <a href='https://ios.yacreader.com'>iOS</a> 或 <a href='https://android.yacreader.com'>Android</a> 版本。 + + + SmartListDialog + + + New smart list + 新增智慧清單 + - enable the server - 啟用伺服器 + + Edit smart list + 編輯智慧清單 + + + + Search filters and syntax + 搜尋篩選器與語法 + + + + Name: + 名稱: + + + + Search: + 搜尋: SortVolumeComics - - Please, sort the list of comics on the left until it matches the comics' information. + + Please sort the list of comics on the left until it matches the comics' information. 請在左側對漫畫列表進行排序,直到它與漫畫的資訊相符。 - + sort comics to match comic information 排序漫畫以匹配漫畫資訊 - + issues - 發行 + 期 - + remove selected comics 移除所選漫畫 - + restore all removed comics 恢復所有移除的漫畫 + + + Add the %n other issue(s) of this volume as missing comics + + 將此卷的其他 %n 期新增為缺少的漫畫 + + ThemeEditorDialog @@ -4409,7 +5691,7 @@ Use quotes to include spaces in a value. i - 我 + i @@ -4424,7 +5706,7 @@ Use quotes to include spaces in a value. Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original. - 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。發布後恢復原樣。 + 按住可在 UI 中閃爍所選值(洋紅色/切換/0↔10)。放開後恢復原樣。 @@ -4434,12 +5716,12 @@ Use quotes to include spaces in a value. Light - 亮度 + 淺色 Dark - 黑暗的 + 深色 @@ -4459,27 +5741,27 @@ Use quotes to include spaces in a value. Theme info - 主題訊息 + 主題資訊 Parameter - 範圍 + 參數 Value - 價值 + 值 Save and apply - 儲存並應用 + 儲存並套用 Export to file... - 匯出到文件... + 匯出到檔案... @@ -4504,7 +5786,7 @@ Use quotes to include spaces in a value. true - 真的 + true @@ -4512,12 +5794,12 @@ Use quotes to include spaces in a value. false - 錯誤的 + false Double-click to toggle - 按兩下切換 + 雙擊切換 @@ -4545,13 +5827,13 @@ Use quotes to include spaces in a value. Save failed - 保存失敗 + 儲存失敗 Could not open file for writing: %1 - 無法開啟文件進行寫入: + 無法開啟檔案進行寫入: %1 @@ -4591,14 +5873,14 @@ Use quotes to include spaces in a value. SEARCH - 搜索 + 搜尋 UpdateLibraryDialog - Updating.... + Updating... 更新中... @@ -4612,6 +5894,14 @@ Use quotes to include spaces in a value. 更新庫 + + ViewSelector + + + View + + + VolumeComicsModel @@ -4630,12 +5920,12 @@ Use quotes to include spaces in a value. issues - 發行 + 期 publisher - 出版者 + 出版社 @@ -4643,7 +5933,7 @@ Use quotes to include spaces in a value. Presets: - 預設: + 預設: @@ -4673,12 +5963,12 @@ Use quotes to include spaces in a value. Show advanced settings - 顯示高級選項 + 顯示進階選項 Custom: - 自定義: + 自訂: @@ -4703,7 +5993,7 @@ Use quotes to include spaces in a value. Zoom - 縮放 + Zoom @@ -4723,12 +6013,12 @@ Use quotes to include spaces in a value. Visibility - 透明度 + 可見度 Light - 亮度 + 光照 @@ -4738,22 +6028,22 @@ Use quotes to include spaces in a value. Low Performance - 低性能 + 低效能 High Performance - 高性能 + 高效能 - Use VSync (improve the image quality in fullscreen mode, worse performance) - 使用VSync (在全屏模式下提高圖像品質, 性能更差) + Use VSync (improves image quality in fullscreen mode, but reduces performance) + 使用 VSync(在全螢幕模式下提高影像品質,但會降低效能) Performance: - 性能: + 效能: @@ -4761,17 +6051,17 @@ Use quotes to include spaces in a value. &Restore - 複位(&R) + 還原(&R) Systray - 系統託盤 + 系統匣 YACReaderLibrary will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray icon. - YACReaderLibrary 將繼續在系統託盤中運行. 想要終止程式, 請在系統託盤圖示的上下文菜單中選擇<b>退出</b>. + YACReaderLibrary 將繼續在系統匣中執行。想要終止程式,請在系統匣圖示的快顯功能表中選擇<b>退出</b>。 @@ -4787,34 +6077,74 @@ Use quotes to include spaces in a value. 舊版本 + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 書庫資料庫遺失。請先還原備份或重新建立書庫,再重新掃描 XML 資訊。 + + + + Unable to load the archive backend: %1 + 無法載入壓縮檔後端:%1 + + + + Unable to start an XML rescan transaction. + 無法啟動 XML 重新掃描交易。 + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 選取的資料夾已不在此書庫中。請重新載入書庫後再掃描。 + + + + Unable to read comics for the XML rescan. + 無法讀取用於 XML 重新掃描的漫畫。 + + + + Unable to commit XML metadata changes. + 無法提交 XML 中繼資料變更。 + + + + Unable to update XML metadata for %1. + 無法更新 %1 的 XML 中繼資料。 + + YACReaderFieldEdit - + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 YACReaderFieldPlainTextEdit + + - - - + + + Click to overwrite 點擊以覆蓋 Restore to default - 恢復默認 + 恢復預設 @@ -4822,7 +6152,7 @@ Use quotes to include spaces in a value. Save - 保存 + 儲存 @@ -4842,12 +6172,12 @@ Use quotes to include spaces in a value. Edit shortcuts - 編輯快捷鍵 + 編輯快速鍵 Shortcuts - 快捷鍵 + 快速鍵 @@ -4860,7 +6190,7 @@ Use quotes to include spaces in a value. type to search - 搜索類型 + 輸入以搜尋 diff --git a/YACReaderLibraryServer/CMakeLists.txt b/YACReaderLibraryServer/CMakeLists.txt index 8341d9d9d..b6b0f0be9 100644 --- a/YACReaderLibraryServer/CMakeLists.txt +++ b/YACReaderLibraryServer/CMakeLists.txt @@ -24,14 +24,20 @@ if(BUILD_NUMBER) endif() # Translations +set(yacreaderlibraryserver_translation_source_targets + YACReaderLibraryServer + library_common + db_helper + comic_backend +) qt_add_translations(YACReaderLibraryServer - SOURCE_TARGETS - YACReaderLibraryServer - comic_backend + SOURCE_TARGETS ${yacreaderlibraryserver_translation_source_targets} + LUPDATE_OPTIONS -no-obsolete TS_FILES yacreaderlibraryserver_es.ts yacreaderlibraryserver_ru.ts yacreaderlibraryserver_pt.ts + yacreaderlibraryserver_pt_PT.ts yacreaderlibraryserver_fr.ts yacreaderlibraryserver_nl.ts yacreaderlibraryserver_tr.ts @@ -40,7 +46,13 @@ qt_add_translations(YACReaderLibraryServer yacreaderlibraryserver_zh_TW.ts yacreaderlibraryserver_zh_HK.ts yacreaderlibraryserver_ko.ts - yacreaderlibraryserver_source.ts + yacreaderlibraryserver_it.ts +) +qt_add_lupdate( + SOURCE_TARGETS ${yacreaderlibraryserver_translation_source_targets} + OPTIONS -no-obsolete + TS_FILES yacreaderlibraryserver_source.ts + LUPDATE_TARGET YACReaderLibraryServer_source_lupdate ) target_link_libraries(YACReaderLibraryServer PRIVATE @@ -72,7 +84,7 @@ endif() # Platform-specific if(WIN32) if(MSVC) - target_link_options(YACReaderLibraryServer PRIVATE "/MANIFESTINPUT:${PROJECT_SOURCE_DIR}/cmake/windows/yacreader.manifest") + target_sources(YACReaderLibraryServer PRIVATE ${PROJECT_SOURCE_DIR}/cmake/windows/yacreader.manifest) endif() target_link_libraries(YACReaderLibraryServer PRIVATE oleaut32 ole32 shell32 user32) endif() diff --git a/YACReaderLibraryServer/console_ui_library_creator.cpp b/YACReaderLibraryServer/console_ui_library_creator.cpp index ab6839889..52c50af1d 100644 --- a/YACReaderLibraryServer/console_ui_library_creator.cpp +++ b/YACReaderLibraryServer/console_ui_library_creator.cpp @@ -1,6 +1,7 @@ #include "console_ui_library_creator.h" #include "comic_info_repairer.h" +#include "data_base_management.h" #include "library_creator.h" #include "xml_info_library_scanner.h" #include "yacreader_libraries.h" @@ -10,36 +11,67 @@ using namespace YACReader; +namespace { +void printLockRecoveryHint() +{ + std::cout << "Wait for active maintenance to finish, or close the application holding the lock. If maintenance was interrupted, check that no application is using this library on any computer, then retry with --remove-stale-lock." << std::endl; +} + +bool recoverMaintenanceIfRequested(const QString &path, bool requested) +{ + if (!requested) + return true; + LibraryMaintenanceLock lock(path); + QString error; + if (!lock.tryLock(true)) { + std::cout << "Recovery failed: " << lock.errorString().toStdString() << std::endl; + return false; + } + if (!DataBaseManagement::recoverInterruptedRestore(path, &error, true)) { + std::cout << "Recovery failed: " << error.toStdString() << std::endl; + return false; + } + return true; +} +} + ConsoleUILibraryCreator::ConsoleUILibraryCreator(QSettings *settings, QObject *parent) : QObject(parent), numComicsProcessed(0), settings(settings) { } -void ConsoleUILibraryCreator::createLibrary(const QString &name, const QString &path) +bool ConsoleUILibraryCreator::createLibrary(const QString &name, const QString &path, bool recoverLock) { + operationFailed = false; QDir pathDir(path); if (!pathDir.exists()) { std::cout << "Directory not found." << std::endl; - return; + return false; } QEventLoop eventLoop; - LibraryCreator *libraryCreator = new LibraryCreator(settings); + LibraryCreator worker(settings); + auto libraryCreator = &worker; QString cleanPath = QDir::cleanPath(pathDir.absolutePath()); YACReaderLibraries yacreaderLibraries; yacreaderLibraries.load(); if (yacreaderLibraries.contains(name)) { std::cout << "A Library named \"" << name.toUtf8().constData() << "\" already exists in database." << std::endl; - delete libraryCreator; - return; + return false; } libraryCreator->createLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath)); + if (QDir(LibraryPaths::libraryDataPath(cleanPath)).exists() && !recoverMaintenanceIfRequested(cleanPath, recoverLock)) + return false; connect(libraryCreator, &LibraryCreator::finished, this, &ConsoleUILibraryCreator::done); connect(libraryCreator, &LibraryCreator::comicAdded, this, &ConsoleUILibraryCreator::newComic); connect(libraryCreator, &LibraryCreator::failedCreatingDB, this, &ConsoleUILibraryCreator::manageCreatingError); + connect(libraryCreator, &LibraryCreator::failedOpeningDB, this, &ConsoleUILibraryCreator::manageCreatingError); + bool completed = false; + connect(libraryCreator, &LibraryCreator::created, &eventLoop, [&] { completed = true; }); + connect(libraryCreator, &LibraryCreator::updated, &eventLoop, [&] { completed = true; }); connect(libraryCreator, &LibraryCreator::finished, &eventLoop, &QEventLoop::quit); @@ -47,12 +79,23 @@ void ConsoleUILibraryCreator::createLibrary(const QString &name, const QString & libraryCreator->start(); eventLoop.exec(); + libraryCreator->wait(); + + if (operationFailed || !completed) { + if (libraryCreator->maintenanceFailure().error == QLockFile::LockFailedError) + printLockRecoveryHint(); + return false; + } yacreaderLibraries.addLibrary(name, cleanPath); - yacreaderLibraries.save(); + if (!yacreaderLibraries.save()) { + std::cout << "The library was created, but its registration could not be saved." << std::endl; + return false; + } + return true; } -bool ConsoleUILibraryCreator::updateLibrary(const QString &path) +bool ConsoleUILibraryCreator::updateLibrary(const QString &path, bool recoverLock) { operationFailed = false; QDir pathDir(path); @@ -62,8 +105,11 @@ bool ConsoleUILibraryCreator::updateLibrary(const QString &path) } QEventLoop eventLoop; - LibraryCreator *libraryCreator = new LibraryCreator(settings); + LibraryCreator worker(settings); + auto libraryCreator = &worker; QString cleanPath = QDir::cleanPath(pathDir.absolutePath()); + if (!recoverMaintenanceIfRequested(cleanPath, recoverLock)) + return false; libraryCreator->updateLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath)); @@ -72,12 +118,17 @@ bool ConsoleUILibraryCreator::updateLibrary(const QString &path) connect(libraryCreator, &LibraryCreator::failedOpeningDB, this, &ConsoleUILibraryCreator::manageUpdatingError); connect(libraryCreator, &LibraryCreator::finished, &eventLoop, &QEventLoop::quit); + bool completed = false; + connect(libraryCreator, &LibraryCreator::updated, &eventLoop, [&] { completed = true; }); std::cout << "Processing comics"; libraryCreator->start(); eventLoop.exec(); - return !operationFailed; + libraryCreator->wait(); + if (libraryCreator->maintenanceFailure().error == QLockFile::LockFailedError) + printLockRecoveryHint(); + return !operationFailed && completed; } void ConsoleUILibraryCreator::addExistingLibrary(const QString &name, const QString &path) @@ -121,20 +172,28 @@ void ConsoleUILibraryCreator::removeLibrary(const QString &name) std::cout << "Library removed : " << name.toUtf8().constData() << std::endl; } -void ConsoleUILibraryCreator::rescanXMLInfoLibrary(const QString &path) +bool ConsoleUILibraryCreator::rescanXMLInfoLibrary(const QString &path, bool recoverLock) { + operationFailed = false; QDir pathDir(path); if (!pathDir.exists()) { std::cout << "Directory not found." << std::endl; - return; + return false; } QEventLoop eventLoop; - XMLInfoLibraryScanner *scanner = new XMLInfoLibraryScanner(); + XMLInfoLibraryScanner worker; + auto scanner = &worker; QString cleanPath = QDir::cleanPath(pathDir.absolutePath()); + if (!recoverMaintenanceIfRequested(cleanPath, recoverLock)) + return false; connect(scanner, &XMLInfoLibraryScanner::finished, this, &ConsoleUILibraryCreator::done); connect(scanner, &XMLInfoLibraryScanner::comicScanned, this, &ConsoleUILibraryCreator::newComic); + connect(scanner, &XMLInfoLibraryScanner::failed, this, [this](const QString &error) { + operationFailed = true; + std::cout << "XML rescan failed: " << error.toStdString() << std::endl; + }); connect(scanner, &XMLInfoLibraryScanner::finished, &eventLoop, &QEventLoop::quit); @@ -142,9 +201,13 @@ void ConsoleUILibraryCreator::rescanXMLInfoLibrary(const QString &path) scanner->scanLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath)); eventLoop.exec(); + scanner->wait(); + if (scanner->maintenanceFailure().error == QLockFile::LockFailedError) + printLockRecoveryHint(); + return !operationFailed && scanner->completedSuccessfully(); } -int ConsoleUILibraryCreator::repairLibrary(const QString &path) +int ConsoleUILibraryCreator::repairLibrary(const QString &path, bool recoverLock) { QDir pathDir(path); if (!pathDir.exists()) { @@ -155,6 +218,10 @@ int ConsoleUILibraryCreator::repairLibrary(const QString &path) QEventLoop eventLoop; ComicInfoRepairer *repairer = new ComicInfoRepairer(settings); const auto cleanPath = QDir::cleanPath(pathDir.absolutePath()); + if (!recoverMaintenanceIfRequested(cleanPath, recoverLock)) { + delete repairer; + return 1; + } connect(repairer, &ComicInfoRepairer::comicProcessed, this, &ConsoleUILibraryCreator::newComic); connect(repairer, &QThread::finished, &eventLoop, &QEventLoop::quit); @@ -169,38 +236,11 @@ int ConsoleUILibraryCreator::repairLibrary(const QString &path) auto summary = runRepair(false); if (summary.lockedByAnotherProcess) { - if (summary.lockHolderIsRunningLocally) { - std::cout << std::endl - << "A repair of this library is already running (" << summary.lockHolderInfo.toStdString() << "). Wait for it to finish." << std::endl; - delete repairer; - return 1; - } - - std::cout << std::endl; - if (summary.lockHolderInfo.isEmpty()) { - std::cout << "The library is locked by a repair that did not finish." << std::endl; - } else { - std::cout << "The library is locked by a repair started by " << summary.lockHolderInfo.toStdString() << "." << std::endl; - } - std::cout << "If you are sure that no other repair is running, the lock can be removed." << std::endl - << "Remove the lock and continue? [y/N] " << std::flush; - - std::string answer; - std::getline(std::cin, answer); - // piped input can keep the trailing carriage return on Windows - const auto trimmedAnswer = QString::fromStdString(answer).trimmed().toLower(); - if (trimmedAnswer != "y" && trimmedAnswer != "yes") { - delete repairer; - return 1; - } - - summary = runRepair(true); - if (summary.lockedByAnotherProcess) { - std::cout << std::endl - << "The library is still locked, another process took the lock." << std::endl; - delete repairer; - return 1; - } + std::cout << std::endl + << summary.error.toStdString() << std::endl; + printLockRecoveryHint(); + delete repairer; + return 1; } if (!summary.error.isEmpty()) { @@ -228,6 +268,7 @@ void ConsoleUILibraryCreator::newComic(const QString & /*relativeComicPath*/, co void ConsoleUILibraryCreator::manageCreatingError(const QString &error) { + operationFailed = true; std::cout << std::endl << "Error creating library! " << error.toUtf8().constData() << std::endl; } @@ -241,7 +282,7 @@ void ConsoleUILibraryCreator::manageUpdatingError(const QString &error) void ConsoleUILibraryCreator::done() { - std::cout << "Done!" << std::endl; + std::cout << (operationFailed ? "Failed." : "Done!") << std::endl; if (numComicsProcessed > 0) std::cout << "Number of comics processed = " << numComicsProcessed << std::endl; diff --git a/YACReaderLibraryServer/console_ui_library_creator.h b/YACReaderLibraryServer/console_ui_library_creator.h index e08f060c6..5b3166cba 100644 --- a/YACReaderLibraryServer/console_ui_library_creator.h +++ b/YACReaderLibraryServer/console_ui_library_creator.h @@ -8,12 +8,12 @@ class ConsoleUILibraryCreator : public QObject Q_OBJECT public: explicit ConsoleUILibraryCreator(QSettings *settings, QObject *parent = 0); - void createLibrary(const QString &name, const QString &path); - bool updateLibrary(const QString &path); + bool createLibrary(const QString &name, const QString &path, bool recoverLock = false); + bool updateLibrary(const QString &path, bool recoverLock = false); void addExistingLibrary(const QString &name, const QString &path); void removeLibrary(const QString &name); - void rescanXMLInfoLibrary(const QString &path); - int repairLibrary(const QString &path); + bool rescanXMLInfoLibrary(const QString &path, bool recoverLock = false); + int repairLibrary(const QString &path, bool recoverLock = false); private: uint numComicsProcessed; diff --git a/YACReaderLibraryServer/main.cpp b/YACReaderLibraryServer/main.cpp index 02588c00e..2de7a849b 100644 --- a/YACReaderLibraryServer/main.cpp +++ b/YACReaderLibraryServer/main.cpp @@ -80,9 +80,9 @@ int main(int argc, char **argv) auto settingsPath = YACReader::getSettingsPath() + "/" + QCoreApplication::applicationName() + ".ini"; QCommandLineParser parser; - parser.setApplicationDescription(QString(QCoreApplication::tr("\nYACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary.\n\n" - "This appplication supports persistent settings, to set them up edit this file %1\n" - "To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md")) + parser.setApplicationDescription(QString(QCoreApplication::tr("\nYACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary.\n\n" + "This application supports persistent settings. To set them up, edit this file: %1\n" + "To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md")) .arg(settingsPath)); parser.addHelpOption(); const QCommandLineOption versionOption = parser.addVersionOption(); @@ -92,7 +92,7 @@ int main(int argc, char **argv) parser.addOption({ "system-info", "Prints detailed information about the system environment, including OS version, hardware specifications, and available resources." }); parser.addOption({ "update-library", "Run a full library update after restoring a backup." }); parser.addOption({ "allow-invalid-current", "Allow restore when the current library database is invalid." }); - parser.addOption({ "remove-stale-lock", "Remove a stale maintenance lock after validating that it is stale." }); + parser.addOption({ "remove-stale-lock", "Attempt maintenance lock recovery. Use only after checking no maintenance operation is running on any computer." }); parser.parse(app.arguments()); const QStringList args = parser.positionalArguments(); @@ -304,10 +304,8 @@ int createLibrary(QCoreApplication &app, QCommandLineParser &parser, QSettings * return 1; } - ConsoleUILibraryCreator *libraryCreatorUI = new ConsoleUILibraryCreator(settings); - libraryCreatorUI->createLibrary(args.at(1), args.at(2)); - - return 0; + ConsoleUILibraryCreator libraryCreatorUI(settings); + return libraryCreatorUI.createLibrary(args.at(1), args.at(2), parser.isSet("remove-stale-lock")) ? 0 : 1; } // ----------------------------------------------------------------------------- @@ -327,7 +325,7 @@ int updateLibrary(QCoreApplication &app, QCommandLineParser &parser, QSettings * } ConsoleUILibraryCreator *libraryCreatorUI = new ConsoleUILibraryCreator(settings); - return libraryCreatorUI->updateLibrary(args.at(1)) ? 0 : 1; + return libraryCreatorUI->updateLibrary(args.at(1), parser.isSet("remove-stale-lock")) ? 0 : 1; } int backupLibrary(QCoreApplication &app, QCommandLineParser &parser, QTextStream &qout) @@ -462,7 +460,7 @@ int repairLibrary(QCoreApplication &app, QCommandLineParser &parser, QSettings * } ConsoleUILibraryCreator libraryCreatorUI(settings); - return libraryCreatorUI.repairLibrary(args.at(1)); + return libraryCreatorUI.repairLibrary(args.at(1), parser.isSet("remove-stale-lock")); } // ----------------------------------------------------------------------------- @@ -573,10 +571,8 @@ int rescanXmlInfo(QCoreApplication &app, QCommandLineParser &parser, QSettings * return 1; } - ConsoleUILibraryCreator *libraryCreatorUI = new ConsoleUILibraryCreator(settings); - libraryCreatorUI->rescanXMLInfoLibrary(args.at(1)); - - return 0; + ConsoleUILibraryCreator libraryCreatorUI(settings); + return libraryCreatorUI.rescanXMLInfoLibrary(args.at(1), parser.isSet("remove-stale-lock")) ? 0 : 1; } // ----------------------------------------------------------------------------- diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_de.ts b/YACReaderLibraryServer/yacreaderlibraryserver_de.ts index b535caa4d..6db29ef8f 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_de.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_de.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + Der Ordnereintrag wurde in der Datenbank der Bibliothek nicht gefunden. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - CRC Error auf Seite (%1): Einige Seiten werden nicht korrekt dargestellt + CRC-Fehler auf Seite (%1): Einige Seiten werden nicht korrekt dargestellt - + Unknown error opening the file - Unbekannter Fehler beim Öffnen des Files + Unbekannter Fehler beim Öffnen der Datei - + 7z not found 7z nicht gefunden - - + + Format not supported Format wird nicht unterstützt + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Der Bibliotheksdatenordner konnte nicht erstellt werden: %1. Prüfen Sie den Ordnerzugriff und den freien Speicherplatz. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Der Bibliotheksdatenordner fehlt: %1. Stellen Sie ihn wieder her oder erstellen Sie die Bibliothek vor der Aktualisierung neu. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Die Bibliotheksdatenbank fehlt: %1. Stellen Sie eine Sicherung wieder her oder erstellen Sie die Bibliothek vor der Aktualisierung neu. + + + + + Unable to start a library database transaction: %1 + Eine Bibliotheksdatenbanktransaktion konnte nicht gestartet werden: %1 + + + + + Unable to commit the library database: %1 + Die Bibliotheksdatenbanktransaktion konnte nicht bestätigt werden: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Der Besitzer der Bibliothekssperre hat sich geändert. Wiederholen Sie den Vorgang und prüfen Sie den neuen Besitzer, bevor Sie den Zugriff wiederherstellen. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Die Wartungssperre konnte nicht entfernt werden: %1. Vergewissern Sie sich, dass die Datei nicht schreibgeschützt ist und Sie in den Bibliotheksdatenordner schreiben können. Versuchen Sie es dann erneut. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Ein Ordner belegt den Pfad der Wartungssperrdatei: %1. Benennen Sie diesen Ordner um oder verschieben Sie ihn, bevor Sie es erneut versuchen. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Die Bibliothekssperrdatei konnte nicht erstellt werden: %1. Prüfen Sie die Schreibrechte für den Bibliotheksdatenordner. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Der Bibliotheksdatenordner fehlt: %1. Stellen Sie den Ordner wieder her oder erstellen Sie die Bibliothek vor der Aktualisierung neu. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + Die Bibliothek hat eine Wartungssperre, deren Besitzer nicht überprüft werden konnte (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Ein anderer Wartungsvorgang verwendet diese Bibliothek (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Die Bibliothekssperrdatei konnte nicht erstellt werden: %1. Prüfen Sie den freien Speicherplatz und den Zugriff auf den Bibliotheksdatenordner. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md YACReaderLibraryServer ist die Headless-Version (keine GUI) von YACReaderLibrary. -Diese Anwendung unterstützt dauerhafte Einstellungen. Um sie einzurichten, bearbeiten Sie diese Datei %1 -Um mehr über die verfügbaren Einstellungen zu erfahren, lesen Sie bitte die Dokumentation unter https://raw.githubusercontent.com/YACReader/yareader/develop/YACReaderLibraryServer/SETTINGS_README.md +Diese Anwendung unterstützt dauerhafte Einstellungen. Um sie einzurichten, bearbeiten Sie diese Datei: %1 +Um mehr über die verfügbaren Einstellungen zu erfahren, lesen Sie bitte die Dokumentation unter https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + SearchFieldRegistry + + + Text, quoted text + Text, Text in Anführungszeichen + + + + Integer + Ganzzahl + + + + Boolean (true / false) + Boolescher Wert (true / false) + + + + Integer (number of days) + Ganzzahl (Anzahl der Tage) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Aufzählung (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Comictitel + + + + Series name + Serienname + + + + Issue number + Ausgabennummer + + + + Volume identifier + Bandkennung + + + + Reading format + Leseformat + + + + Comic rating + Comicbewertung + + + + Textual tags + Text-Tags + + + + Writer credit + Autor + + + + Penciller credit + Zeichner + + + + Inker credit + Tuscher + + + + Colorist credit + Kolorist + + + + Letterer credit + Letterer + + + + Cover artist credit + Coverzeichner + + + + Editor credit + Lektor + + + + Story arc name + Name des Handlungsbogens + + + + Position within a story arc + Position innerhalb eines Handlungsbogens + + + + Number of issues in a story arc + Anzahl der Ausgaben in einem Handlungsbogen + + + + Characters appearing in the comic + Im Comic vorkommende Figuren + + + + Teams appearing in the comic + Im Comic vorkommende Teams + + + + Locations appearing in the comic + Im Comic vorkommende Orte + + + + Primary character or team + Hauptfigur oder Hauptteam + + + + Comic synopsis + Comic-Zusammenfassung + + + + Publisher name + Name des Verlags + + + + Publishing imprint + Verlagsimprint + + + + Publication format + Veröffentlichungsformat + + + + Recommended age rating + Empfohlene Altersfreigabe + + + + Comic genre + Comicgenre + + + + ISO language code + ISO-Sprachcode + + + + Publication date metadata + Metadaten zum Veröffentlichungsdatum + + + + Series grouping metadata + Metadaten zur Seriengruppierung + + + + Alternate series name + Alternativer Serienname + + + + Alternate issue number + Alternative Ausgabennummer + + + + Alternate series issue count + Anzahl der Ausgaben der alternativen Serie + + + + Number of issues in the series + Anzahl der Ausgaben in der Serie + + + + Whether the comic is marked as read + Ob der Comic als gelesen markiert ist + + + + Whether reading has started + Ob mit dem Lesen begonnen wurde + + + + Whether metadata has been edited + Ob die Metadaten bearbeitet wurden + + + + Whether the comic is in color + Ob der Comic farbig ist + + + + Number of pages + Seitenzahl + + + + Comic file name + Dateiname des Comics + + + + When the item was added + Wann das Element hinzugefügt wurde + + + + When the comic was last opened + Wann der Comic zuletzt geöffnet wurde + + + + Comic notes + Comicnotizen + + + + Review text + Rezensionstext + + + + Parent folder name + Name des übergeordneten Ordners + + + + Default reading format for the folder + Standard-Leseformat des Ordners + + + + Whether the folder is complete + Ob der Ordner vollständig ist + + + + Whether the folder is marked as finished + Ob der Ordner als beendet markiert ist + + + + When the folder was updated + Wann der Ordner aktualisiert wurde + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Die Bibliotheksdatenbank fehlt. Stellen Sie eine Sicherung wieder her oder erstellen Sie die Bibliothek neu, bevor Sie XML-Informationen erneut einlesen. + + + + Unable to load the archive backend: %1 + Das Archiv-Backend konnte nicht geladen werden: %1 + + + + Unable to start an XML rescan transaction. + Eine Transaktion zum erneuten Einlesen der XML-Informationen konnte nicht gestartet werden. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Der ausgewählte Ordner ist nicht mehr in dieser Bibliothek. Laden Sie die Bibliothek vor dem erneuten Einlesen neu. + + + + Unable to read comics for the XML rescan. + Comics für das erneute Einlesen der XML-Informationen konnten nicht gelesen werden. + + + + Unable to commit XML metadata changes. + Änderungen an XML-Metadaten konnten nicht bestätigt werden. + + + + Unable to update XML metadata for %1. + XML-Metadaten für %1 konnten nicht aktualisiert werden. diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_es.ts b/YACReaderLibraryServer/yacreaderlibraryserver_es.ts index 566329d24..3d55e312f 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_es.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_es.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + No se ha encontrado la entrada de la carpeta en la base de datos de la biblioteca. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly Error CRC en la página (%1): algunas de las páginas no se mostrarán correctamente - + Unknown error opening the file - Error desconocido abriendo el archivo + Error desconocido al abrir el archivo - + 7z not found 7z no encontrado - - + + Format not supported Formato no soportado + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + No se pudo crear la carpeta de datos de la biblioteca: %1. Comprueba el acceso a la carpeta y el espacio disponible. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Falta la carpeta de datos de la biblioteca: %1. Restáurala o vuelve a crear la biblioteca antes de actualizar. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Falta la base de datos de la biblioteca: %1. Restaura una copia de seguridad o vuelve a crear la biblioteca antes de actualizar. + + + + + Unable to start a library database transaction: %1 + No se pudo iniciar una transacción en la base de datos de la biblioteca: %1 + + + + + Unable to commit the library database: %1 + No se pudo confirmar la transacción en la base de datos de la biblioteca: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + El propietario del bloqueo de la biblioteca ha cambiado. Reintenta la operación y revisa el nuevo propietario antes de recuperar el acceso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + No se pudo eliminar el bloqueo de mantenimiento: %1. Asegúrate de que el archivo no sea de solo lectura y de que puedas escribir en la carpeta de datos de la biblioteca, y vuelve a intentarlo. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Una carpeta ocupa la ruta del archivo de bloqueo de mantenimiento: %1. Renómbrala o muévela antes de reintentar. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + No se pudo crear el archivo de bloqueo de la biblioteca: %1. Comprueba los permisos de escritura de la carpeta de datos de la biblioteca. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Falta la carpeta de datos de la biblioteca: %1. Restaura la carpeta o vuelve a crear la biblioteca antes de actualizarla. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + La biblioteca tiene un bloqueo de mantenimiento cuyo propietario no se pudo verificar (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Otra operación de mantenimiento está usando esta biblioteca (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + No se pudo crear el archivo de bloqueo de la biblioteca: %1. Comprueba el espacio disponible y el acceso a la carpeta de datos de la biblioteca. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md YACReaderLibraryServer es la versión sin interfaz gráfica (headless) de YACReaderLibrary. -Esta aplicación admite ajustes persistentes; para configurarlos, edita este archivo %1 +Esta aplicación admite ajustes persistentes; para configurarlos, edita este archivo: %1 Para conocer los ajustes disponibles, consulta la documentación en https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + SearchFieldRegistry + + + Text, quoted text + Texto, texto entre comillas + + + + Integer + Entero + + + + Boolean (true / false) + Booleano (true / false) + + + + Integer (number of days) + Entero (número de días) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Enumeración (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Título del cómic + + + + Series name + Nombre de la serie + + + + Issue number + Número del ejemplar + + + + Volume identifier + Identificador del volumen + + + + Reading format + Formato de lectura + + + + Comic rating + Valoración del cómic + + + + Textual tags + Etiquetas de texto + + + + Writer credit + Crédito de guion + + + + Penciller credit + Crédito de dibujo + + + + Inker credit + Crédito de entintado + + + + Colorist credit + Crédito de color + + + + Letterer credit + Crédito de rotulación + + + + Cover artist credit + Crédito de portada + + + + Editor credit + Crédito de edición + + + + Story arc name + Nombre del arco argumental + + + + Position within a story arc + Posición dentro de un arco argumental + + + + Number of issues in a story arc + Número de ejemplares del arco argumental + + + + Characters appearing in the comic + Personajes que aparecen en el cómic + + + + Teams appearing in the comic + Equipos que aparecen en el cómic + + + + Locations appearing in the comic + Lugares que aparecen en el cómic + + + + Primary character or team + Personaje o equipo principal + + + + Comic synopsis + Sinopsis del cómic + + + + Publisher name + Nombre de la editorial + + + + Publishing imprint + Sello editorial + + + + Publication format + Formato de publicación + + + + Recommended age rating + Clasificación de edad recomendada + + + + Comic genre + Género del cómic + + + + ISO language code + Código de idioma ISO + + + + Publication date metadata + Metadatos de la fecha de publicación + + + + Series grouping metadata + Metadatos de agrupación de series + + + + Alternate series name + Nombre alternativo de la serie + + + + Alternate issue number + Número alternativo del ejemplar + + + + Alternate series issue count + Cantidad de ejemplares de la serie alternativa + + + + Number of issues in the series + Número de ejemplares de la serie + + + + Whether the comic is marked as read + Si el cómic está marcado como leído + + + + Whether reading has started + Si la lectura ha comenzado + + + + Whether metadata has been edited + Si se han editado los metadatos + + + + Whether the comic is in color + Si el cómic es a color + + + + Number of pages + Número de páginas + + + + Comic file name + Nombre del archivo del cómic + + + + When the item was added + Cuándo se añadió el elemento + + + + When the comic was last opened + Cuándo se abrió el cómic por última vez + + + + Comic notes + Notas del cómic + + + + Review text + Texto de la reseña + + + + Parent folder name + Nombre de la carpeta superior + + + + Default reading format for the folder + Formato de lectura predeterminado de la carpeta + + + + Whether the folder is complete + Si la carpeta está completa + + + + Whether the folder is marked as finished + Si la carpeta está marcada como finalizada + + + + When the folder was updated + Cuándo se actualizó la carpeta + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Falta la base de datos de la biblioteca. Restaura una copia de seguridad o vuelve a crear la biblioteca antes de reescanear la información XML. + + + + Unable to load the archive backend: %1 + No se pudo cargar el motor de archivos comprimidos: %1 + + + + Unable to start an XML rescan transaction. + No se pudo iniciar una transacción de reescaneo XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + La carpeta seleccionada ya no está en esta biblioteca. Vuelve a cargar la biblioteca antes de reescanear. + + + + Unable to read comics for the XML rescan. + No se pudieron leer los cómics para el reescaneo XML. + + + + Unable to commit XML metadata changes. + No se pudieron confirmar los cambios en los metadatos XML. + + + + Unable to update XML metadata for %1. + No se pudieron actualizar los metadatos XML de %1. + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_fr.ts b/YACReaderLibraryServer/yacreaderlibraryserver_fr.ts index 5260499d2..20e1cea8e 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_fr.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_fr.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + L'entrée du dossier est introuvable dans la base de données de la bibliothèque. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - Erreur CRC sur la page (%1): certaines pages ne s'afficheront pas correctement + Erreur CRC sur la page (%1) : certaines pages ne s'afficheront pas correctement - + Unknown error opening the file Erreur inconnue lors de l'ouverture du fichier - + 7z not found 7z introuvable - - + + Format not supported Format non supporté + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Impossible de créer le dossier de données de la bibliothèque : %1. Vérifiez les droits d'accès et l'espace disque disponible. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Le dossier de données de la bibliothèque est manquant : %1. Restaurez-le ou recréez la bibliothèque avant de la mettre à jour. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + La base de données de la bibliothèque est manquante : %1. Restaurez une sauvegarde ou recréez la bibliothèque avant de la mettre à jour. + + + + + Unable to start a library database transaction: %1 + Impossible de démarrer une transaction dans la base de données de la bibliothèque : %1 + + + + + Unable to commit the library database: %1 + Impossible de valider la transaction dans la base de données de la bibliothèque : %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Le propriétaire du verrou de la bibliothèque a changé. Relancez l'opération et vérifiez le nouveau propriétaire avant de rétablir l'accès. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Impossible de supprimer le verrou de maintenance : %1. Vérifiez que le fichier n'est pas en lecture seule et que vous pouvez écrire dans le dossier de données de la bibliothèque, puis réessayez. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Un dossier occupe le chemin du fichier de verrouillage de maintenance : %1. Renommez ou déplacez ce dossier avant de réessayer. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Impossible de créer le fichier de verrouillage de la bibliothèque : %1. Vérifiez les droits d'écriture dans le dossier de données de la bibliothèque. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Le dossier de données de la bibliothèque est manquant : %1. Restaurez le dossier ou recréez la bibliothèque avant de la mettre à jour. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + La bibliothèque possède un verrou de maintenance dont le propriétaire n'a pas pu être vérifié (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Une autre opération de maintenance utilise cette bibliothèque (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Impossible de créer le fichier de verrouillage de la bibliothèque : %1. Vérifiez l'espace disque disponible et l'accès au dossier de données de la bibliothèque. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer est la version sans tête (sans interface graphique) de YACReaderLibrary. +YACReaderLibraryServer est la version headless (sans interface graphique) de YACReaderLibrary. Cette application prend en charge les paramètres persistants, pour les configurer, modifiez ce fichier %1 Pour en savoir plus sur les paramètres disponibles, veuillez consulter la documentation sur https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + SearchFieldRegistry + + + Text, quoted text + Texte, texte entre guillemets + + + + Integer + Entier + + + + Boolean (true / false) + Booléen (true / false) + + + + Integer (number of days) + Entier (nombre de jours) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Énumération (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Titre de la bande dessinée + + + + Series name + Nom de la série + + + + Issue number + Numéro du fascicule + + + + Volume identifier + Identifiant du volume + + + + Reading format + Format de lecture + + + + Comic rating + Note de la bande dessinée + + + + Textual tags + Étiquettes textuelles + + + + Writer credit + Crédit du scénariste + + + + Penciller credit + Crédit du dessinateur + + + + Inker credit + Crédit de l'encreur + + + + Colorist credit + Crédit du coloriste + + + + Letterer credit + Crédit du lettreur + + + + Cover artist credit + Crédit de l'artiste de couverture + + + + Editor credit + Crédit du responsable éditorial + + + + Story arc name + Nom de l'arc narratif + + + + Position within a story arc + Position dans un arc narratif + + + + Number of issues in a story arc + Nombre de fascicules dans un arc narratif + + + + Characters appearing in the comic + Personnages apparaissant dans la bande dessinée + + + + Teams appearing in the comic + Équipes apparaissant dans la bande dessinée + + + + Locations appearing in the comic + Lieux apparaissant dans la bande dessinée + + + + Primary character or team + Personnage ou équipe principale + + + + Comic synopsis + Synopsis de la bande dessinée + + + + Publisher name + Nom de la maison d'édition + + + + Publishing imprint + Label éditorial + + + + Publication format + Format de publication + + + + Recommended age rating + Classification d'âge recommandée + + + + Comic genre + Genre de la bande dessinée + + + + ISO language code + Code de langue ISO + + + + Publication date metadata + Métadonnées de date de publication + + + + Series grouping metadata + Métadonnées de regroupement des séries + + + + Alternate series name + Autre nom de la série + + + + Alternate issue number + Autre numéro de fascicule + + + + Alternate series issue count + Nombre de fascicules de l'autre série + + + + Number of issues in the series + Nombre de fascicules dans la série + + + + Whether the comic is marked as read + Si la bande dessinée est marquée comme lue + + + + Whether reading has started + Si la lecture a commencé + + + + Whether metadata has been edited + Si les métadonnées ont été modifiées + + + + Whether the comic is in color + Si la bande dessinée est en couleur + + + + Number of pages + Nombre de pages + + + + Comic file name + Nom du fichier de la bande dessinée + + + + When the item was added + Date d'ajout de l'élément + + + + When the comic was last opened + Dernière ouverture de la bande dessinée + + + + Comic notes + Notes de la bande dessinée + + + + Review text + Texte de la critique + + + + Parent folder name + Nom du dossier parent + + + + Default reading format for the folder + Format de lecture par défaut du dossier + + + + Whether the folder is complete + Si le dossier est complet + + + + Whether the folder is marked as finished + Si le dossier est marqué comme terminé + + + + When the folder was updated + Date de mise à jour du dossier + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + La base de données de la bibliothèque est manquante. Restaurez une sauvegarde ou recréez la bibliothèque avant de relancer l'analyse des informations XML. + + + + Unable to load the archive backend: %1 + Impossible de charger le moteur d'archives : %1 + + + + Unable to start an XML rescan transaction. + Impossible de démarrer une transaction de nouvelle analyse XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Le dossier sélectionné n'est plus dans cette bibliothèque. Rechargez la bibliothèque avant de relancer l'analyse. + + + + Unable to read comics for the XML rescan. + Impossible de lire les BD pour la nouvelle analyse XML. + + + + Unable to commit XML metadata changes. + Impossible de valider les modifications des métadonnées XML. + + + + Unable to update XML metadata for %1. + Impossible de mettre à jour les métadonnées XML de %1. + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_it.ts b/YACReaderLibraryServer/yacreaderlibraryserver_it.ts new file mode 100644 index 000000000..d895eae37 --- /dev/null +++ b/YACReaderLibraryServer/yacreaderlibraryserver_it.ts @@ -0,0 +1,436 @@ + + + + + DBHelper + + + The folder entry could not be found in the library database. + La voce della cartella non è stata trovata nel database della libreria. + + + + FileComic + + + CRC error on page (%1): some of the pages will not be displayed correctly + Errore CRC alla pagina (%1): alcune pagine non saranno visualizzate correttamente + + + + Unknown error opening the file + Errore sconosciuto aprendo il file + + + + 7z not found + 7z non trovato + + + + + Format not supported + Formato non supportato + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Impossibile creare la cartella dati della libreria: %1. Controlla l'accesso alla cartella e lo spazio disponibile sul disco. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + La cartella dati della libreria è mancante: %1. Ripristinala o ricrea la libreria prima di aggiornarla. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Il database della libreria è mancante: %1. Ripristina un backup o ricrea la libreria prima di aggiornarla. + + + + + Unable to start a library database transaction: %1 + Impossibile avviare una transazione nel database della libreria: %1 + + + + + Unable to commit the library database: %1 + Impossibile confermare la transazione nel database della libreria: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Il proprietario del blocco della libreria è cambiato. Riprova l'operazione e verifica il nuovo proprietario prima di recuperare l'accesso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Impossibile rimuovere il blocco di manutenzione: %1. Assicurati che il file non sia di sola lettura e di poter scrivere nella cartella dati della libreria, quindi riprova. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Una cartella occupa il percorso del file di blocco di manutenzione: %1. Rinominala o spostala prima di riprovare. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Impossibile creare il file di blocco della libreria: %1. Controlla i permessi di scrittura della cartella dati della libreria. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + La cartella dati della libreria è mancante: %1. Ripristina la cartella o ricrea la libreria prima di aggiornarla. + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Impossibile creare il file di blocco della libreria: %1. Controlla lo spazio disponibile sul disco e l'accesso alla cartella dati della libreria. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + La libreria ha un blocco di manutenzione il cui proprietario non è stato verificato (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Un'altra operazione di manutenzione sta usando questa libreria (%1)%2 + + + + QCoreApplication + + + +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. + +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + +YACReaderLibraryServer è la versione headless (senza interfaccia grafica) di YACReaderLibrary. + +Questa applicazione supporta impostazioni persistenti. Per configurarle, modifica questo file: %1 +Per conoscere le impostazioni disponibili, consulta la documentazione all'indirizzo https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + SearchFieldRegistry + + + Text, quoted text + Testo, testo tra virgolette + + + + Integer + Intero + + + + Boolean (true / false) + Booleano (true / false) + + + + Integer (number of days) + Intero (numero di giorni) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Enumerazione (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Titolo del fumetto + + + + Series name + Nome della serie + + + + Issue number + Numero dell'albo + + + + Volume identifier + Identificatore del volume + + + + Reading format + Formato di lettura + + + + Comic rating + Valutazione del fumetto + + + + Textual tags + Tag testuali + + + + Writer credit + Credito dello sceneggiatore + + + + Penciller credit + Credito del disegnatore + + + + Inker credit + Credito dell'inchiostratore + + + + Colorist credit + Credito del colorista + + + + Letterer credit + Credito del letterista + + + + Cover artist credit + Credito dell'artista di copertina + + + + Editor credit + Credito del curatore + + + + Story arc name + Nome dell'arco narrativo + + + + Position within a story arc + Posizione nell'arco narrativo + + + + Number of issues in a story arc + Numero di albi nell'arco narrativo + + + + Characters appearing in the comic + Personaggi presenti nel fumetto + + + + Teams appearing in the comic + Squadre presenti nel fumetto + + + + Locations appearing in the comic + Luoghi presenti nel fumetto + + + + Primary character or team + Personaggio o squadra principale + + + + Comic synopsis + Sinossi del fumetto + + + + Publisher name + Nome dell'editore + + + + Publishing imprint + Marchio editoriale + + + + Publication format + Formato di pubblicazione + + + + Recommended age rating + Classificazione per età consigliata + + + + Comic genre + Genere del fumetto + + + + ISO language code + Codice lingua ISO + + + + Publication date metadata + Metadati della data di pubblicazione + + + + Series grouping metadata + Metadati di raggruppamento delle serie + + + + Alternate series name + Nome alternativo della serie + + + + Alternate issue number + Numero alternativo dell'albo + + + + Alternate series issue count + Numero di albi della serie alternativa + + + + Number of issues in the series + Numero di albi nella serie + + + + Whether the comic is marked as read + Se il fumetto è contrassegnato come letto + + + + Whether reading has started + Se la lettura è iniziata + + + + Whether metadata has been edited + Se i metadati sono stati modificati + + + + Whether the comic is in color + Se il fumetto è a colori + + + + Number of pages + Numero di pagine + + + + Comic file name + Nome del file del fumetto + + + + When the item was added + Quando l'elemento è stato aggiunto + + + + When the comic was last opened + Quando il fumetto è stato aperto l'ultima volta + + + + Comic notes + Note del fumetto + + + + Review text + Testo della recensione + + + + Parent folder name + Nome della cartella superiore + + + + Default reading format for the folder + Formato di lettura predefinito della cartella + + + + Whether the folder is complete + Se la cartella è completa + + + + Whether the folder is marked as finished + Se la cartella è contrassegnata come terminata + + + + When the folder was updated + Quando la cartella è stata aggiornata + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Il database della libreria è mancante. Ripristina un backup o ricrea la libreria prima di ripetere la scansione delle informazioni XML. + + + + Unable to load the archive backend: %1 + Impossibile caricare il backend degli archivi: %1 + + + + Unable to start an XML rescan transaction. + Impossibile avviare una transazione per la nuova scansione XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + La cartella selezionata non è più in questa libreria. Ricarica la libreria prima di ripetere la scansione. + + + + Unable to read comics for the XML rescan. + Impossibile leggere i fumetti per la nuova scansione XML. + + + + Unable to commit XML metadata changes. + Impossibile confermare le modifiche ai metadati XML. + + + + Unable to update XML metadata for %1. + Impossibile aggiornare i metadati XML di %1. + + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_ko.ts b/YACReaderLibraryServer/yacreaderlibraryserver_ko.ts index 53d91b328..9e289a6c5 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_ko.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_ko.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + 라이브러리 데이터베이스에서 폴더 항목을 찾을 수 없습니다. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly %1번 페이지에서 CRC 오류 발생: 일부 페이지가 올바르게 표시되지 않을 수 있습니다 - + Unknown error opening the file 파일을 여는 중 알 수 없는 오류가 발생했습니다 - + 7z not found 7z를 찾을 수 없습니다 - - + + Format not supported 지원하지 않는 형식입니다 + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 라이브러리 데이터 폴더를 만들 수 없습니다: %1. 폴더 접근 권한과 디스크 여유 공간을 확인하세요. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 라이브러리 데이터 폴더가 없습니다: %1. 업데이트하기 전에 폴더를 복원하거나 라이브러리를 다시 만드세요. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 라이브러리 데이터베이스가 없습니다: %1. 업데이트하기 전에 백업을 복원하거나 라이브러리를 다시 만드세요. + + + + + Unable to start a library database transaction: %1 + 라이브러리 데이터베이스 트랜잭션을 시작할 수 없습니다: %1 + + + + + Unable to commit the library database: %1 + 라이브러리 데이터베이스 트랜잭션을 커밋할 수 없습니다: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 라이브러리 잠금 소유자가 변경되었습니다. 작업을 다시 시도하고 새 소유자를 확인한 후 접근 권한을 복구하세요. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 유지 관리 잠금을 제거할 수 없습니다: %1. 파일이 읽기 전용이 아니며 라이브러리 데이터 폴더에 쓰기 권한이 있는지 확인한 후 다시 시도하세요. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 유지 관리 잠금 파일 경로를 폴더가 차지하고 있습니다: %1. 폴더 이름을 바꾸거나 이동한 후 다시 시도하세요. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 라이브러리 잠금 파일을 만들 수 없습니다: %1. 라이브러리 데이터 폴더의 쓰기 권한을 확인하세요. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 라이브러리 데이터 폴더가 없습니다: %1. 폴더를 복원하거나 라이브러리를 다시 만든 후 업데이트하세요. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 이 라이브러리의 유지 관리 잠금 소유자를 확인할 수 없습니다(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 다른 유지 관리 작업에서 이 라이브러리를 사용 중입니다(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 라이브러리 잠금 파일을 만들 수 없습니다: %1. 디스크 여유 공간과 라이브러리 데이터 폴더 접근 권한을 확인하세요. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer는 YACReaderLibrary의 헤드리스(GUI 없는) 버전입니다. +YACReaderLibraryServer는 YACReaderLibrary의 headless(GUI 없는) 버전입니다. 이 응용 프로그램은 영구 설정을 지원합니다. 설정을 변경하려면 다음 파일을 편집하세요: %1 사용 가능한 설정 문서는 다음에서 확인할 수 있습니다: https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + SearchFieldRegistry + + + Text, quoted text + 텍스트, 따옴표로 묶은 텍스트 + + + + Integer + 정수 + + + + Boolean (true / false) + 부울 값 (true / false) + + + + Integer (number of days) + 정수 (일수) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + 열거형 (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + 만화 제목 + + + + Series name + 시리즈 이름 + + + + Issue number + 호 번호 + + + + Volume identifier + 볼륨 식별자 + + + + Reading format + 읽기 형식 + + + + Comic rating + 만화 평점 + + + + Textual tags + 텍스트 태그 + + + + Writer credit + 글 작가 크레딧 + + + + Penciller credit + 밑그림 작가 크레딧 + + + + Inker credit + 펜선 작가 크레딧 + + + + Colorist credit + 채색 작가 크레딧 + + + + Letterer credit + 식자 작가 크레딧 + + + + Cover artist credit + 표지 작가 크레딧 + + + + Editor credit + 편집자 크레딧 + + + + Story arc name + 스토리 아크 이름 + + + + Position within a story arc + 스토리 아크 내 위치 + + + + Number of issues in a story arc + 스토리 아크의 호 수 + + + + Characters appearing in the comic + 만화에 등장하는 등장인물 + + + + Teams appearing in the comic + 만화에 등장하는 팀 + + + + Locations appearing in the comic + 만화에 등장하는 장소 + + + + Primary character or team + 주요 등장인물 또는 팀 + + + + Comic synopsis + 만화 줄거리 + + + + Publisher name + 출판사 이름 + + + + Publishing imprint + 출판 브랜드 + + + + Publication format + 출판 형식 + + + + Recommended age rating + 권장 연령 등급 + + + + Comic genre + 만화 장르 + + + + ISO language code + ISO 언어 코드 + + + + Publication date metadata + 출판일 메타데이터 + + + + Series grouping metadata + 시리즈 그룹화 메타데이터 + + + + Alternate series name + 대체 시리즈 이름 + + + + Alternate issue number + 대체 호 번호 + + + + Alternate series issue count + 대체 시리즈의 호 수 + + + + Number of issues in the series + 시리즈의 호 수 + + + + Whether the comic is marked as read + 만화를 읽음으로 표시했는지 여부 + + + + Whether reading has started + 읽기를 시작했는지 여부 + + + + Whether metadata has been edited + 메타데이터를 편집했는지 여부 + + + + Whether the comic is in color + 만화가 컬러인지 여부 + + + + Number of pages + 페이지 수 + + + + Comic file name + 만화 파일 이름 + + + + When the item was added + 항목을 추가한 시점 + + + + When the comic was last opened + 만화를 마지막으로 연 시점 + + + + Comic notes + 만화 메모 + + + + Review text + 리뷰 텍스트 + + + + Parent folder name + 상위 폴더 이름 + + + + Default reading format for the folder + 폴더의 기본 읽기 형식 + + + + Whether the folder is complete + 폴더가 완전한지 여부 + + + + Whether the folder is marked as finished + 폴더를 완료로 표시했는지 여부 + + + + When the folder was updated + 폴더를 업데이트한 시점 + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 라이브러리 데이터베이스가 없습니다. XML 정보를 다시 검색하기 전에 백업을 복원하거나 라이브러리를 다시 만드세요. + + + + Unable to load the archive backend: %1 + 압축 파일 백엔드를 불러올 수 없습니다: %1 + + + + Unable to start an XML rescan transaction. + XML 재검색 트랜잭션을 시작할 수 없습니다. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 선택한 폴더가 더 이상 이 라이브러리에 없습니다. 다시 검색하기 전에 라이브러리를 새로 불러오세요. + + + + Unable to read comics for the XML rescan. + XML 재검색을 위한 만화를 읽을 수 없습니다. + + + + Unable to commit XML metadata changes. + XML 메타데이터 변경 사항을 커밋할 수 없습니다. + + + + Unable to update XML metadata for %1. + %1의 XML 메타데이터를 업데이트할 수 없습니다. + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_nl.ts b/YACReaderLibraryServer/yacreaderlibraryserver_nl.ts index 30b0741fd..b574fe709 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_nl.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_nl.ts @@ -1,39 +1,120 @@ + + DBHelper + + + The folder entry could not be found in the library database. + De mapvermelding is niet gevonden in de database van de bibliotheek. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly CRC-fout op pagina (%1): sommige pagina's worden niet correct weergegeven - + Unknown error opening the file Onbekende fout bij het openen van het bestand - + 7z not found - 7Z Archiefbestand niet gevonden + 7z niet gevonden - - + + Format not supported Formaat niet ondersteund + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Kan de gegevensmap van de bibliotheek niet maken: %1. Controleer de toegang tot de map en de beschikbare schijfruimte. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + De gegevensmap van de bibliotheek ontbreekt: %1. Herstel deze of maak de bibliotheek opnieuw aan voordat u bijwerkt. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + De bibliotheekdatabase ontbreekt: %1. Herstel een back-up of maak de bibliotheek opnieuw aan voordat u bijwerkt. + + + + + Unable to start a library database transaction: %1 + Kan geen transactie in de bibliotheekdatabase starten: %1 + + + + + Unable to commit the library database: %1 + Kan de transactie in de bibliotheekdatabase niet vastleggen: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + De eigenaar van de bibliotheekvergrendeling is gewijzigd. Probeer de taak opnieuw en controleer de nieuwe eigenaar voordat u de toegang herstelt. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Kan de onderhoudsvergrendeling niet verwijderen: %1. Controleer of het bestand niet alleen-lezen is en of u naar de gegevensmap van de bibliotheek kunt schrijven. Probeer het daarna opnieuw. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Een map neemt het pad van het onderhoudsvergrendelingsbestand in: %1. Hernoem of verplaats die map voordat u het opnieuw probeert. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Kan het bibliotheekvergrendelingsbestand niet maken: %1. Controleer de schrijfrechten voor de gegevensmap van de bibliotheek. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + De gegevensmap van de bibliotheek ontbreekt: %1. Herstel de map of maak de bibliotheek opnieuw aan voordat u deze bijwerkt. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + De bibliotheek heeft een onderhoudsvergrendeling waarvan de eigenaar niet kon worden geverifieerd (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Een andere onderhoudstaak gebruikt deze bibliotheek (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Kan het bibliotheekvergrendelingsbestand niet maken: %1. Controleer de beschikbare schijfruimte en de toegang tot de gegevensmap van de bibliotheek. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md YACReaderLibraryServer is de headless (geen gui) versie van YACReaderLibrary. @@ -41,4 +122,315 @@ Deze applicatie ondersteunt permanente instellingen. Om ze in te stellen, bewerk Voor meer informatie over de beschikbare instellingen kunt u de documentatie raadplegen op https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + SearchFieldRegistry + + + Text, quoted text + Tekst, tekst tussen aanhalingstekens + + + + Integer + Geheel getal + + + + Boolean (true / false) + Booleaans (true / false) + + + + Integer (number of days) + Geheel getal (aantal dagen) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Opsomming (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Striptitel + + + + Series name + Serienaam + + + + Issue number + Uitgavenummer + + + + Volume identifier + Volumeaanduiding + + + + Reading format + Leesindeling + + + + Comic rating + Stripwaardering + + + + Textual tags + Teksttags + + + + Writer credit + Vermelding van de schrijver + + + + Penciller credit + Vermelding van de tekenaar + + + + Inker credit + Vermelding van de inkter + + + + Colorist credit + Vermelding van de inkleurder + + + + Letterer credit + Vermelding van de letteraar + + + + Cover artist credit + Vermelding van de omslagtekenaar + + + + Editor credit + Vermelding van de redacteur + + + + Story arc name + Naam van de verhaallijn + + + + Position within a story arc + Positie binnen een verhaallijn + + + + Number of issues in a story arc + Aantal uitgaven in een verhaallijn + + + + Characters appearing in the comic + Personages die in de strip voorkomen + + + + Teams appearing in the comic + Teams die in de strip voorkomen + + + + Locations appearing in the comic + Locaties die in de strip voorkomen + + + + Primary character or team + Hoofdpersonage of hoofdteam + + + + Comic synopsis + Samenvatting van de strip + + + + Publisher name + Naam van de uitgever + + + + Publishing imprint + Uitgeversimprint + + + + Publication format + Publicatie-indeling + + + + Recommended age rating + Aanbevolen leeftijdsclassificatie + + + + Comic genre + Stripgenre + + + + ISO language code + ISO-taalcode + + + + Publication date metadata + Metagegevens van de publicatiedatum + + + + Series grouping metadata + Metagegevens voor seriegroepering + + + + Alternate series name + Alternatieve serienaam + + + + Alternate issue number + Alternatief uitgavenummer + + + + Alternate series issue count + Aantal uitgaven in de alternatieve serie + + + + Number of issues in the series + Aantal uitgaven in de serie + + + + Whether the comic is marked as read + Of de strip als gelezen is gemarkeerd + + + + Whether reading has started + Of het lezen is begonnen + + + + Whether metadata has been edited + Of de metagegevens zijn bewerkt + + + + Whether the comic is in color + Of de strip in kleur is + + + + Number of pages + Aantal pagina's + + + + Comic file name + Bestandsnaam van de strip + + + + When the item was added + Wanneer het item is toegevoegd + + + + When the comic was last opened + Wanneer de strip voor het laatst is geopend + + + + Comic notes + Stripnotities + + + + Review text + Recensietekst + + + + Parent folder name + Naam van de bovenliggende map + + + + Default reading format for the folder + Standaard leesindeling voor de map + + + + Whether the folder is complete + Of de map compleet is + + + + Whether the folder is marked as finished + Of de map als voltooid is gemarkeerd + + + + When the folder was updated + Wanneer de map is bijgewerkt + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + De bibliotheekdatabase ontbreekt. Herstel een back-up of maak de bibliotheek opnieuw aan voordat u de XML-informatie opnieuw scant. + + + + Unable to load the archive backend: %1 + Kan de archiefbackend niet laden: %1 + + + + Unable to start an XML rescan transaction. + Kan geen transactie voor het opnieuw scannen van XML starten. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + De geselecteerde map staat niet meer in deze bibliotheek. Herlaad de bibliotheek voordat u opnieuw scant. + + + + Unable to read comics for the XML rescan. + Kan strips voor het opnieuw scannen van XML niet lezen. + + + + Unable to commit XML metadata changes. + Kan wijzigingen in XML-metadata niet vastleggen. + + + + Unable to update XML metadata for %1. + Kan XML-metadata voor %1 niet bijwerken. + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_pt.ts b/YACReaderLibraryServer/yacreaderlibraryserver_pt.ts index 3a05d96b9..8bda63349 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_pt.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_pt.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + A entrada da pasta não foi encontrada no banco de dados da biblioteca. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly Erro CRC na página (%1): algumas páginas não serão exibidas corretamente - + Unknown error opening the file Erro desconhecido ao abrir o arquivo - + 7z not found 7z não encontrado - - + + Format not supported Formato não suportado + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Não foi possível criar a pasta de dados da biblioteca: %1. Verifique o acesso à pasta e o espaço disponível em disco. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + A pasta de dados da biblioteca está ausente: %1. Restaure-a ou recrie a biblioteca antes de atualizar. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + O banco de dados da biblioteca está ausente: %1. Restaure um backup ou recrie a biblioteca antes de atualizar. + + + + + Unable to start a library database transaction: %1 + Não foi possível iniciar uma transação no banco de dados da biblioteca: %1 + + + + + Unable to commit the library database: %1 + Não foi possível confirmar a transação no banco de dados da biblioteca: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + O proprietário do bloqueio da biblioteca mudou. Tente a operação novamente e verifique o novo proprietário antes de recuperar o acesso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Não foi possível remover o bloqueio de manutenção: %1. Confirme que o arquivo não é somente leitura e que você pode gravar na pasta de dados da biblioteca, e tente novamente. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Uma pasta ocupa o caminho do arquivo de bloqueio de manutenção: %1. Renomeie ou mova essa pasta antes de tentar novamente. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Não foi possível criar o arquivo de bloqueio da biblioteca: %1. Verifique as permissões de gravação da pasta de dados da biblioteca. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + A pasta de dados da biblioteca está ausente: %1. Restaure a pasta ou recrie a biblioteca antes de atualizá-la. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + A biblioteca tem um bloqueio de manutenção cujo proprietário não pôde ser verificado (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Outra operação de manutenção está usando esta biblioteca (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Não foi possível criar o arquivo de bloqueio da biblioteca: %1. Verifique o espaço disponível em disco e o acesso à pasta de dados da biblioteca. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer é a versão sem cabeça (sem interface gráfica) do YACReaderLibrary. +YACReaderLibraryServer é a versão headless (sem interface gráfica) do YACReaderLibrary. Este aplicativo suporta configurações persistentes, para configurá-las edite este arquivo %1 Para saber mais sobre as configurações disponíveis, verifique a documentação em https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + SearchFieldRegistry + + + Text, quoted text + Texto, texto entre aspas + + + + Integer + Inteiro + + + + Boolean (true / false) + Booleano (true / false) + + + + Integer (number of days) + Inteiro (número de dias) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Enumeração (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Título do quadrinho + + + + Series name + Nome da série + + + + Issue number + Número da edição + + + + Volume identifier + Identificador do volume + + + + Reading format + Formato de leitura + + + + Comic rating + Avaliação do quadrinho + + + + Textual tags + Tags de texto + + + + Writer credit + Crédito do roteirista + + + + Penciller credit + Crédito do desenhista + + + + Inker credit + Crédito do arte-finalista + + + + Colorist credit + Crédito do colorista + + + + Letterer credit + Crédito do letrista + + + + Cover artist credit + Crédito do artista da capa + + + + Editor credit + Crédito do editor + + + + Story arc name + Nome do arco da história + + + + Position within a story arc + Posição dentro de um arco da história + + + + Number of issues in a story arc + Número de edições em um arco da história + + + + Characters appearing in the comic + Personagens presentes no quadrinho + + + + Teams appearing in the comic + Equipes presentes no quadrinho + + + + Locations appearing in the comic + Locais presentes no quadrinho + + + + Primary character or team + Personagem ou equipe principal + + + + Comic synopsis + Sinopse do quadrinho + + + + Publisher name + Nome da editora + + + + Publishing imprint + Selo editorial + + + + Publication format + Formato da publicação + + + + Recommended age rating + Classificação etária recomendada + + + + Comic genre + Gênero do quadrinho + + + + ISO language code + Código de idioma ISO + + + + Publication date metadata + Metadados da data de publicação + + + + Series grouping metadata + Metadados de agrupamento de séries + + + + Alternate series name + Nome alternativo da série + + + + Alternate issue number + Número alternativo da edição + + + + Alternate series issue count + Quantidade de edições da série alternativa + + + + Number of issues in the series + Número de edições da série + + + + Whether the comic is marked as read + Se o quadrinho está marcado como lido + + + + Whether reading has started + Se a leitura foi iniciada + + + + Whether metadata has been edited + Se os metadados foram editados + + + + Whether the comic is in color + Se o quadrinho é colorido + + + + Number of pages + Número de páginas + + + + Comic file name + Nome do arquivo do quadrinho + + + + When the item was added + Quando o item foi adicionado + + + + When the comic was last opened + Quando o quadrinho foi aberto pela última vez + + + + Comic notes + Notas do quadrinho + + + + Review text + Texto da resenha + + + + Parent folder name + Nome da pasta principal + + + + Default reading format for the folder + Formato de leitura padrão da pasta + + + + Whether the folder is complete + Se a pasta está completa + + + + Whether the folder is marked as finished + Se a pasta está marcada como finalizada + + + + When the folder was updated + Quando a pasta foi atualizada + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + O banco de dados da biblioteca está ausente. Restaure um backup ou recrie a biblioteca antes de verificar novamente as informações XML. + + + + Unable to load the archive backend: %1 + Não foi possível carregar o mecanismo de arquivos compactados: %1 + + + + Unable to start an XML rescan transaction. + Não foi possível iniciar uma transação de nova varredura XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + A pasta selecionada não está mais nesta biblioteca. Recarregue a biblioteca antes de verificar novamente. + + + + Unable to read comics for the XML rescan. + Não foi possível ler os quadrinhos para a nova varredura XML. + + + + Unable to commit XML metadata changes. + Não foi possível confirmar as alterações nos metadados XML. + + + + Unable to update XML metadata for %1. + Não foi possível atualizar os metadados XML de %1. + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_pt_PT.ts b/YACReaderLibraryServer/yacreaderlibraryserver_pt_PT.ts new file mode 100644 index 000000000..f64a21fb2 --- /dev/null +++ b/YACReaderLibraryServer/yacreaderlibraryserver_pt_PT.ts @@ -0,0 +1,436 @@ + + + + + DBHelper + + + The folder entry could not be found in the library database. + A entrada da pasta não foi encontrada na base de dados da biblioteca. + + + + FileComic + + + CRC error on page (%1): some of the pages will not be displayed correctly + Erro de CRC na página (%1): algumas páginas não serão apresentadas corretamente + + + + Unknown error opening the file + Erro desconhecido ao abrir o ficheiro + + + + 7z not found + 7z não encontrado + + + + + Format not supported + Formato não suportado + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Não foi possível criar a pasta de dados da biblioteca: %1. Verifique o acesso à pasta e o espaço disponível em disco. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + A pasta de dados da biblioteca está em falta: %1. Restaure-a ou recrie a biblioteca antes de atualizar. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + A base de dados da biblioteca está em falta: %1. Restaure uma cópia de segurança ou recrie a biblioteca antes de atualizar. + + + + + Unable to start a library database transaction: %1 + Não foi possível iniciar uma transação na base de dados da biblioteca: %1 + + + + + Unable to commit the library database: %1 + Não foi possível confirmar a transação na base de dados da biblioteca: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + O proprietário do bloqueio da biblioteca mudou. Repita a operação e verifique o novo proprietário antes de recuperar o acesso. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Não foi possível remover o bloqueio de manutenção: %1. Confirme que o ficheiro não é só de leitura e que pode escrever na pasta de dados da biblioteca, e tente novamente. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Uma pasta ocupa o caminho do ficheiro de bloqueio de manutenção: %1. Mude o nome ou mova essa pasta antes de tentar novamente. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Não foi possível criar o ficheiro de bloqueio da biblioteca: %1. Verifique as permissões de escrita da pasta de dados da biblioteca. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + A pasta de dados da biblioteca está em falta: %1. Restaure a pasta ou recrie a biblioteca antes de a atualizar. + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Não foi possível criar o ficheiro de bloqueio da biblioteca: %1. Verifique o espaço disponível em disco e o acesso à pasta de dados da biblioteca. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + A biblioteca tem um bloqueio de manutenção cujo proprietário não pôde ser verificado (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Outra operação de manutenção está a utilizar esta biblioteca (%1)%2 + + + + QCoreApplication + + + +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. + +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + +O YACReaderLibraryServer é a versão headless (sem GUI) do YACReaderLibrary. + +Esta aplicação suporta definições persistentes; para as configurar, edite este ficheiro %1 +Para saber mais sobre as definições disponíveis, consulte a documentação em https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + SearchFieldRegistry + + + Text, quoted text + Texto, texto entre aspas + + + + Integer + Inteiro + + + + Boolean (true / false) + Booleano (true / false) + + + + Integer (number of days) + Inteiro (número de dias) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Enumeração (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Título da banda desenhada + + + + Series name + Nome da série + + + + Issue number + Número da edição + + + + Volume identifier + Identificador do volume + + + + Reading format + Formato de leitura + + + + Comic rating + Classificação da banda desenhada + + + + Textual tags + Etiquetas de texto + + + + Writer credit + Crédito de argumentista + + + + Penciller credit + Crédito de desenhador + + + + Inker credit + Crédito de arte-finalista + + + + Colorist credit + Crédito de colorista + + + + Letterer credit + Crédito de letrista + + + + Cover artist credit + Crédito de artista da capa + + + + Editor credit + Crédito de editor + + + + Story arc name + Nome do arco narrativo + + + + Position within a story arc + Posição dentro de um arco narrativo + + + + Number of issues in a story arc + Número de edições num arco narrativo + + + + Characters appearing in the comic + Personagens que aparecem na banda desenhada + + + + Teams appearing in the comic + Equipas que aparecem na banda desenhada + + + + Locations appearing in the comic + Locais que aparecem na banda desenhada + + + + Primary character or team + Personagem ou equipa principal + + + + Comic synopsis + Sinopse da banda desenhada + + + + Publisher name + Nome da editora + + + + Publishing imprint + Chancela editorial da publicação + + + + Publication format + Formato da publicação + + + + Recommended age rating + Classificação etária recomendada + + + + Comic genre + Género da banda desenhada + + + + ISO language code + Código de idioma ISO + + + + Publication date metadata + Metadados da data de publicação + + + + Series grouping metadata + Metadados de agrupamento de séries + + + + Alternate series name + Nome alternativo da série + + + + Alternate issue number + Número alternativo da edição + + + + Alternate series issue count + Quantidade de edições da série alternativa + + + + Number of issues in the series + Número de edições da série + + + + Whether the comic is marked as read + Se a banda desenhada está marcada como lida + + + + Whether reading has started + Se a leitura foi iniciada + + + + Whether metadata has been edited + Se os metadados foram editados + + + + Whether the comic is in color + Se a banda desenhada é a cores + + + + Number of pages + Número de páginas + + + + Comic file name + Nome do ficheiro da banda desenhada + + + + When the item was added + Quando o item foi adicionado + + + + When the comic was last opened + Quando a banda desenhada foi aberta pela última vez + + + + Comic notes + Notas da banda desenhada + + + + Review text + Texto da crítica + + + + Parent folder name + Nome da pasta de nível superior + + + + Default reading format for the folder + Formato de leitura predefinido da pasta + + + + Whether the folder is complete + Se a pasta está completa + + + + Whether the folder is marked as finished + Se a pasta está marcada como concluída + + + + When the folder was updated + Quando a pasta foi atualizada + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + A base de dados da biblioteca está em falta. Restaure uma cópia de segurança ou recrie a biblioteca antes de voltar a analisar as informações XML. + + + + Unable to load the archive backend: %1 + Não foi possível carregar o motor de arquivos comprimidos: %1 + + + + Unable to start an XML rescan transaction. + Não foi possível iniciar uma transação de nova análise XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + A pasta selecionada já não está nesta biblioteca. Recarregue a biblioteca antes de voltar a analisar. + + + + Unable to read comics for the XML rescan. + Não foi possível ler as bandas desenhadas para a nova análise XML. + + + + Unable to commit XML metadata changes. + Não foi possível confirmar as alterações nos metadados XML. + + + + Unable to update XML metadata for %1. + Não foi possível atualizar os metadados XML de %1. + + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_ru.ts b/YACReaderLibraryServer/yacreaderlibraryserver_ru.ts index 6376c40bf..8932ffc56 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_ru.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_ru.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + Запись о папке не найдена в базе данных библиотеки. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly Ошибка контрольной суммы CRC на странице (%1): некоторые страницы будут отображаться неправильно - + Unknown error opening the file Неизвестная ошибка при открытии файла - + 7z not found 7z не найден - - + + Format not supported Формат не поддерживается + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Не удалось создать папку данных библиотеки: %1. Проверьте права доступа и свободное место на диске. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Отсутствует папка данных библиотеки: %1. Восстановите её или создайте библиотеку заново перед обновлением. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Отсутствует база данных библиотеки: %1. Восстановите резервную копию или создайте библиотеку заново перед обновлением. + + + + + Unable to start a library database transaction: %1 + Не удалось начать транзакцию базы данных библиотеки: %1 + + + + + Unable to commit the library database: %1 + Не удалось зафиксировать транзакцию базы данных библиотеки: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Владелец блокировки библиотеки изменился. Повторите операцию и проверьте нового владельца перед восстановлением доступа. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Не удалось удалить блокировку обслуживания: %1. Убедитесь, что файл не доступен только для чтения и у вас есть право записи в папку данных библиотеки, затем повторите попытку. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Путь файла блокировки обслуживания занят папкой: %1. Переименуйте или переместите эту папку перед повторной попыткой. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Не удалось создать файл блокировки библиотеки: %1. Проверьте права записи в папку данных библиотеки. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Отсутствует папка данных библиотеки: %1. Восстановите папку или создайте библиотеку заново перед обновлением. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + У библиотеки есть блокировка обслуживания, владельца которой не удалось проверить (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Эту библиотеку использует другая операция обслуживания (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Не удалось создать файл блокировки библиотеки: %1. Проверьте свободное место на диске и доступ к папке данных библиотеки. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer — это безголовая (без графического интерфейса) версия YACReaderLibrary. +YACReaderLibraryServer — это версия headless (без графического интерфейса) YACReaderLibrary. -Это приложение поддерживает постоянные настройки. Чтобы настроить их, отредактируйте этот файл %1. -Чтобы узнать о доступных настройках, ознакомьтесь с документацией по адресу https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md. +Это приложение поддерживает постоянные настройки. Чтобы настроить их, отредактируйте этот файл: %1 +Чтобы узнать о доступных настройках, ознакомьтесь с документацией по адресу https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + SearchFieldRegistry + + + Text, quoted text + Текст, текст в кавычках + + + + Integer + Целое число + + + + Boolean (true / false) + Логическое значение (true / false) + + + + Integer (number of days) + Целое число (количество дней) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Перечисление (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Название комикса + + + + Series name + Название серии + + + + Issue number + Номер выпуска + + + + Volume identifier + Идентификатор тома + + + + Reading format + Формат чтения + + + + Comic rating + Оценка комикса + + + + Textual tags + Текстовые теги + + + + Writer credit + Автор сценария + + + + Penciller credit + Художник-карандашист + + + + Inker credit + Контуровщик + + + + Colorist credit + Колорист + + + + Letterer credit + Леттерер + + + + Cover artist credit + Художник обложки + + + + Editor credit + Редактор + + + + Story arc name + Название сюжетной арки + + + + Position within a story arc + Позиция в сюжетной арке + + + + Number of issues in a story arc + Количество выпусков в сюжетной арке + + + + Characters appearing in the comic + Персонажи, появляющиеся в комиксе + + + + Teams appearing in the comic + Команды, появляющиеся в комиксе + + + + Locations appearing in the comic + Локации, появляющиеся в комиксе + + + + Primary character or team + Главный персонаж или команда + + + + Comic synopsis + Краткое содержание комикса + + + + Publisher name + Название издательства + + + + Publishing imprint + Импринт издательства + + + + Publication format + Формат публикации + + + + Recommended age rating + Рекомендуемый возрастной рейтинг + + + + Comic genre + Жанр комикса + + + + ISO language code + Код языка ISO + + + + Publication date metadata + Метаданные даты публикации + + + + Series grouping metadata + Метаданные группировки серий + + + + Alternate series name + Альтернативное название серии + + + + Alternate issue number + Альтернативный номер выпуска + + + + Alternate series issue count + Количество выпусков альтернативной серии + + + + Number of issues in the series + Количество выпусков в серии + + + + Whether the comic is marked as read + Отмечен ли комикс как прочитанный + + + + Whether reading has started + Начато ли чтение + + + + Whether metadata has been edited + Изменялись ли метаданные + + + + Whether the comic is in color + Является ли комикс цветным + + + + Number of pages + Количество страниц + + + + Comic file name + Имя файла комикса + + + + When the item was added + Когда элемент был добавлен + + + + When the comic was last opened + Когда комикс открывался в последний раз + + + + Comic notes + Примечания к комиксу + + + + Review text + Текст рецензии + + + + Parent folder name + Название родительской папки + + + + Default reading format for the folder + Формат чтения папки по умолчанию + + + + Whether the folder is complete + Завершена ли папка + + + + Whether the folder is marked as finished + Отмечена ли папка как законченная + + + + When the folder was updated + Когда папка была обновлена + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + База данных библиотеки отсутствует. Восстановите резервную копию или создайте библиотеку заново перед повторным сканированием XML. + + + + Unable to load the archive backend: %1 + Не удалось загрузить модуль работы с архивами: %1 + + + + Unable to start an XML rescan transaction. + Не удалось начать транзакцию повторного сканирования XML. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Выбранной папки больше нет в этой библиотеке. Перезагрузите библиотеку перед повторным сканированием. + + + + Unable to read comics for the XML rescan. + Не удалось прочитать комиксы для повторного сканирования XML. + + + + Unable to commit XML metadata changes. + Не удалось зафиксировать изменения метаданных XML. + + + + Unable to update XML metadata for %1. + Не удалось обновить метаданные XML для %1. diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_source.ts b/YACReaderLibraryServer/yacreaderlibraryserver_source.ts index 90439fd09..716dafa07 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_source.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_source.ts @@ -1,39 +1,431 @@ + + DBHelper + + + The folder entry could not be found in the library database. + + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - + Unknown error opening the file - + 7z not found - - + + Format not supported + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + + + + + + Unable to start a library database transaction: %1 + + + + + + Unable to commit the library database: %1 + + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + + + + + Another maintenance operation is using this library (%1)%2 + + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + + SearchFieldRegistry + + + Text, quoted text + + + + + Integer + + + + + Boolean (true / false) + + + + + Integer (number of days) + + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + + Comic title + + + + + Series name + + + + + Issue number + + + + + Volume identifier + + + + + Reading format + + + + + Comic rating + + + + + Textual tags + + + + + Writer credit + + + + + Penciller credit + + + + + Inker credit + + + + + Colorist credit + + + + + Letterer credit + + + + + Cover artist credit + + + + + Editor credit + + + + + Story arc name + + + + + Position within a story arc + + + + + Number of issues in a story arc + + + + + Characters appearing in the comic + + + + + Teams appearing in the comic + + + + + Locations appearing in the comic + + + + + Primary character or team + + + + + Comic synopsis + + + + + Publisher name + + + + + Publishing imprint + + + + + Publication format + + + + + Recommended age rating + + + + + Comic genre + + + + + ISO language code + + + + + Publication date metadata + + + + + Series grouping metadata + + + + + Alternate series name + + + + + Alternate issue number + + + + + Alternate series issue count + + + + + Number of issues in the series + + + + + Whether the comic is marked as read + + + + + Whether reading has started + + + + + Whether metadata has been edited + + + + + Whether the comic is in color + + + + + Number of pages + + + + + Comic file name + + + + + When the item was added + + + + + When the comic was last opened + + + + + Comic notes + + + + + Review text + + + + + Parent folder name + + + + + Default reading format for the folder + + + + + Whether the folder is complete + + + + + Whether the folder is marked as finished + + + + + When the folder was updated + + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + + + + + Unable to load the archive backend: %1 + + + + + Unable to start an XML rescan transaction. + + + + + The selected folder is no longer in this library. Reload the library before rescanning. + + + + + Unable to read comics for the XML rescan. + + + + + Unable to commit XML metadata changes. + + + + + Unable to update XML metadata for %1. diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_tr.ts b/YACReaderLibraryServer/yacreaderlibraryserver_tr.ts index 09e393ed0..feb45308a 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_tr.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_tr.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + Klasör kaydı kütüphane veritabanında bulunamadı. + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - (%1). sayfada CRC hatası : bazı sayfalar düzgün görüntülenmeyecek + (%1). sayfada CRC hatası: bazı sayfalar düzgün görüntülenmeyecek - + Unknown error opening the file Dosya açılırken bilinmeyen hata - + 7z not found 7z bulunamadı - - + + Format not supported Biçim desteklenmiyor + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + Kitaplık veri klasörü oluşturulamadı: %1. Klasör erişimini ve kullanılabilir disk alanını kontrol edin. + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + Kitaplık veri klasörü eksik: %1. Güncellemeden önce klasörü geri yükleyin veya kitaplığı yeniden oluşturun. + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + Kitaplık veritabanı eksik: %1. Güncellemeden önce bir yedeği geri yükleyin veya kitaplığı yeniden oluşturun. + + + + + Unable to start a library database transaction: %1 + Kitaplık veritabanı işlemi başlatılamadı: %1 + + + + + Unable to commit the library database: %1 + Kitaplık veritabanı işlemi kalıcılaştırılamadı: %1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + Kitaplık kilidinin sahibi değişti. Erişimi kurtarmadan önce işlemi yeniden deneyip yeni sahibi inceleyin. + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + Bakım kilidi kaldırılamadı: %1. Dosyanın salt okunur olmadığından ve kitaplık veri klasörüne yazabildiğinizden emin olup yeniden deneyin. + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + Bakım kilidi dosyasının yolunu bir klasör kullanıyor: %1. Yeniden denemeden önce bu klasörü yeniden adlandırın veya taşıyın. + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + Kitaplık kilidi dosyası oluşturulamadı: %1. Kitaplık veri klasörünün yazma izinlerini kontrol edin. + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + Kitaplık veri klasörü eksik: %1. Güncellemeden önce klasörü geri yükleyin veya kitaplığı yeniden oluşturun. + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + Kitaplığın sahibi doğrulanamayan bir bakım kilidi var (%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + Başka bir bakım işlemi bu kitaplığı kullanıyor (%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + Kitaplık kilidi dosyası oluşturulamadı: %1. Kullanılabilir disk alanını ve kitaplık veri klasörüne erişimi kontrol edin. + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer, YACReaderLibrary'nin başsız (gui yok) sürümüdür. +YACReaderLibraryServer, YACReaderLibrary'nin headless (arayüzsüz) sürümüdür. -Bu uygulama kalıcı ayarları destekler, bunları ayarlamak için bu dosyayı düzenleyin %1 +Bu uygulama kalıcı ayarları destekler. Bunları ayarlamak için şu dosyayı düzenleyin: %1 Mevcut ayarlar hakkında bilgi edinmek için lütfen https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md adresindeki belgelere bakın. + + SearchFieldRegistry + + + Text, quoted text + Metin, tırnak içinde metin + + + + Integer + Tam sayı + + + + Boolean (true / false) + Mantıksal değer (true / false) + + + + Integer (number of days) + Tam sayı (gün sayısı) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + Numaralandırma (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + Çizgi roman başlığı + + + + Series name + Seri adı + + + + Issue number + Sayı numarası + + + + Volume identifier + Cilt tanımlayıcısı + + + + Reading format + Okuma biçimi + + + + Comic rating + Çizgi roman puanı + + + + Textual tags + Metin etiketleri + + + + Writer credit + Yazar bilgisi + + + + Penciller credit + Çizer bilgisi + + + + Inker credit + Mürekkepleme sanatçısı bilgisi + + + + Colorist credit + Renklendirme sanatçısı bilgisi + + + + Letterer credit + Harfleme sanatçısı bilgisi + + + + Cover artist credit + Kapak sanatçısı bilgisi + + + + Editor credit + Editör bilgisi + + + + Story arc name + Hikâye yayı adı + + + + Position within a story arc + Hikâye yayı içindeki konum + + + + Number of issues in a story arc + Hikâye yayındaki sayı adedi + + + + Characters appearing in the comic + Çizgi romanda görünen karakterler + + + + Teams appearing in the comic + Çizgi romanda görünen ekipler + + + + Locations appearing in the comic + Çizgi romanda görünen yerler + + + + Primary character or team + Ana karakter veya ekip + + + + Comic synopsis + Çizgi roman özeti + + + + Publisher name + Yayınevi adı + + + + Publishing imprint + Yayın markası + + + + Publication format + Yayın biçimi + + + + Recommended age rating + Önerilen yaş derecelendirmesi + + + + Comic genre + Çizgi roman türü + + + + ISO language code + ISO dil kodu + + + + Publication date metadata + Yayın tarihi üst verisi + + + + Series grouping metadata + Seri gruplandırma üst verisi + + + + Alternate series name + Alternatif seri adı + + + + Alternate issue number + Alternatif sayı numarası + + + + Alternate series issue count + Alternatif serideki sayı adedi + + + + Number of issues in the series + Serideki sayı adedi + + + + Whether the comic is marked as read + Çizgi romanın okundu olarak işaretlenip işaretlenmediği + + + + Whether reading has started + Okumaya başlanıp başlanmadığı + + + + Whether metadata has been edited + Üst verinin düzenlenip düzenlenmediği + + + + Whether the comic is in color + Çizgi romanın renkli olup olmadığı + + + + Number of pages + Sayfa sayısı + + + + Comic file name + Çizgi roman dosya adı + + + + When the item was added + Öğenin ne zaman eklendiği + + + + When the comic was last opened + Çizgi romanın en son ne zaman açıldığı + + + + Comic notes + Çizgi roman notları + + + + Review text + İnceleme metni + + + + Parent folder name + Üst klasör adı + + + + Default reading format for the folder + Klasörün varsayılan okuma biçimi + + + + Whether the folder is complete + Klasörün tamamlanmış olup olmadığı + + + + Whether the folder is marked as finished + Klasörün bitmiş olarak işaretlenip işaretlenmediği + + + + When the folder was updated + Klasörün ne zaman güncellendiği + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + Kitaplık veritabanı eksik. XML bilgilerini yeniden taramadan önce bir yedeği geri yükleyin veya kitaplığı yeniden oluşturun. + + + + Unable to load the archive backend: %1 + Arşiv arka ucu yüklenemedi: %1 + + + + Unable to start an XML rescan transaction. + XML yeniden tarama işlemi başlatılamadı. + + + + The selected folder is no longer in this library. Reload the library before rescanning. + Seçili klasör artık bu kitaplıkta değil. Yeniden taramadan önce kitaplığı yeniden yükleyin. + + + + Unable to read comics for the XML rescan. + XML yeniden taraması için çizgi romanlar okunamadı. + + + + Unable to commit XML metadata changes. + XML üst verisi değişiklikleri kalıcılaştırılamadı. + + + + Unable to update XML metadata for %1. + %1 için XML üst verisi güncellenemedi. + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_zh_CN.ts b/YACReaderLibraryServer/yacreaderlibraryserver_zh_CN.ts index 70eb8de75..81f4e4409 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_zh_CN.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_zh_CN.ts @@ -1,44 +1,436 @@ + + DBHelper + + + The folder entry could not be found in the library database. + 在库数据库中找不到该文件夹的记录。 + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 页 CRC 校验失败: 部分页面将无法正确显示 + 第 %1 页 CRC 校验失败:部分页面将无法正确显示 - + Unknown error opening the file 打开文件时出现未知错误 - + 7z not found 未找到 7z - - + + Format not supported 不支持的文件格式 + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 无法创建书库数据文件夹:%1。请检查文件夹访问权限和可用磁盘空间。 + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 书库数据文件夹缺失:%1。请先恢复该文件夹或重新创建书库,再进行更新。 + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 书库数据库缺失:%1。请先恢复备份或重新创建书库,再进行更新。 + + + + + Unable to start a library database transaction: %1 + 无法启动书库数据库事务:%1 + + + + + Unable to commit the library database: %1 + 无法提交书库数据库事务:%1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 书库锁的所有者已更改。请重试操作并检查新的所有者,然后再恢复访问。 + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 无法移除维护锁:%1。请确保文件不是只读的,且您可以写入书库数据文件夹,然后重试。 + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 维护锁文件路径被文件夹占用:%1。请先重命名或移动该文件夹,然后重试。 + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 无法创建书库锁文件:%1。请检查书库数据文件夹的写入权限。 + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 书库数据文件夹缺失:%1。请恢复该文件夹或重新创建书库,然后再更新。 + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 书库存在维护锁,但无法验证其所有者(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 另一个维护操作正在使用此书库(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 无法创建书库锁文件:%1。请检查可用磁盘空间和书库数据文件夹的访问权限。 + + QCoreApplication -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer 是 YACReaderLibrary 的无头(无 GUI)版本。 +YACReaderLibraryServer 是 YACReaderLibrary 的 headless(无 GUI)版本。 此应用程序支持持久设置,要设置它们,请编辑此文件 %1 要了解可用设置,请查看文档:https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + SearchFieldRegistry + + + Text, quoted text + 文本、带引号的文本 + + + + Integer + 整数 + + + + Boolean (true / false) + 布尔值 (true / false) + + + + Integer (number of days) + 整数(天数) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + 枚举 (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + 漫画标题 + + + + Series name + 系列名称 + + + + Issue number + 期号 + + + + Volume identifier + 卷标识 + + + + Reading format + 阅读格式 + + + + Comic rating + 漫画评分 + + + + Textual tags + 文本标签 + + + + Writer credit + 编剧署名 + + + + Penciller credit + 线稿师署名 + + + + Inker credit + 墨线师署名 + + + + Colorist credit + 上色师署名 + + + + Letterer credit + 嵌字师署名 + + + + Cover artist credit + 封面画师署名 + + + + Editor credit + 编辑署名 + + + + Story arc name + 故事线名称 + + + + Position within a story arc + 故事线中的位置 + + + + Number of issues in a story arc + 故事线中的期数 + + + + Characters appearing in the comic + 漫画中出现的角色 + + + + Teams appearing in the comic + 漫画中出现的团队 + + + + Locations appearing in the comic + 漫画中出现的地点 + + + + Primary character or team + 主要角色或团队 + + + + Comic synopsis + 漫画简介 + + + + Publisher name + 出版社名称 + + + + Publishing imprint + 出版品牌 + + + + Publication format + 出版格式 + + + + Recommended age rating + 建议年龄分级 + + + + Comic genre + 漫画题材 + + + + ISO language code + ISO 语言代码 + + + + Publication date metadata + 出版日期元数据 + + + + Series grouping metadata + 系列分组元数据 + + + + Alternate series name + 备用系列名称 + + + + Alternate issue number + 备用期号 + + + + Alternate series issue count + 备用系列期数 + + + + Number of issues in the series + 系列期数 + + + + Whether the comic is marked as read + 漫画是否标记为已读 + + + + Whether reading has started + 是否已开始阅读 + + + + Whether metadata has been edited + 元数据是否已编辑 + + + + Whether the comic is in color + 漫画是否为彩色 + + + + Number of pages + 页数 + + + + Comic file name + 漫画文件名 + + + + When the item was added + 项目的添加时间 + + + + When the comic was last opened + 漫画上次打开时间 + + + + Comic notes + 漫画备注 + + + + Review text + 评论文本 + + + + Parent folder name + 上级文件夹名称 + + + + Default reading format for the folder + 文件夹的默认阅读格式 + + + + Whether the folder is complete + 文件夹是否完整 + + + + Whether the folder is marked as finished + 文件夹是否标记为已完结 + + + + When the folder was updated + 文件夹的更新时间 + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 书库数据库缺失。请先恢复备份或重新创建书库,再重新扫描 XML 信息。 + + + + Unable to load the archive backend: %1 + 无法加载压缩包后端:%1 + + + + Unable to start an XML rescan transaction. + 无法启动 XML 重新扫描事务。 + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 选中的文件夹已不在此书库中。请重新加载书库后再扫描。 + + + + Unable to read comics for the XML rescan. + 无法读取用于 XML 重新扫描的漫画。 + + + + Unable to commit XML metadata changes. + 无法提交 XML 元数据更改。 + + + + Unable to update XML metadata for %1. + 无法更新 %1 的 XML 元数据。 + + diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_zh_HK.ts b/YACReaderLibraryServer/yacreaderlibraryserver_zh_HK.ts index 04b04fbd8..0ae139c27 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_zh_HK.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_zh_HK.ts @@ -1,28 +1,109 @@ + + DBHelper + + + The folder entry could not be found in the library database. + 在庫資料庫中找不到該資料夾的記錄。 + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 頁 CRC 校驗失敗: 部分頁面將無法正確顯示 + 第 %1 頁 CRC 校驗失敗:部分頁面將無法正確顯示 - + Unknown error opening the file - 打開檔時出現未知錯誤 + 開啟檔案時出現未知錯誤 - + 7z not found 未找到 7z - - + + Format not supported - 不支持的檔格式 + 不支援的檔案格式 + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 無法建立書庫資料夾:%1。請檢查資料夾存取權限和可用磁碟空間。 + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 書庫資料夾遺失:%1。請先還原該資料夾或重新建立書庫,再進行更新。 + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 書庫資料庫遺失:%1。請先還原備份或重新建立書庫,再進行更新。 + + + + + Unable to start a library database transaction: %1 + 無法啟動書庫資料庫交易:%1 + + + + + Unable to commit the library database: %1 + 無法提交書庫資料庫交易:%1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 書庫鎖定的擁有者已變更。請重試作業並檢查新的擁有者,然後再恢復存取。 + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 無法移除維護鎖定:%1。請確保檔案不是唯讀的,且您可以寫入書庫資料夾,然後重試。 + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 維護鎖定檔案路徑被資料夾佔用:%1。請先重新命名或移動該資料夾,然後重試。 + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查書庫資料夾的寫入權限。 + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 書庫資料夾遺失:%1。請還原該資料夾或重新建立書庫,然後再更新。 + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 書庫存在維護鎖定,但無法驗證其擁有者(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 另一個維護作業正在使用此書庫(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查可用磁碟空間和書庫資料夾的存取權限。 @@ -30,15 +111,326 @@ -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer 是 YACReaderLibrary 的無頭(無 GUI)版本。 +YACReaderLibraryServer 是 YACReaderLibrary 的 headless(無 GUI)版本。 -此應用程式支援持久性設置,要設定它們,請編輯此文件 %1 -若要了解可用設置,請查看文件:https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +此應用程式支援持久性設定,要設定它們,請編輯此檔案 %1 +若要了解可用設定,請查看文件:https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + SearchFieldRegistry + + + Text, quoted text + 文字、引號內文字 + + + + Integer + 整數 + + + + Boolean (true / false) + 布林值 (true / false) + + + + Integer (number of days) + 整數(日數) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + 列舉 (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + 漫畫標題 + + + + Series name + 系列名稱 + + + + Issue number + 期號 + + + + Volume identifier + 卷冊識別碼 + + + + Reading format + 閱讀格式 + + + + Comic rating + 漫畫評分 + + + + Textual tags + 文字標籤 + + + + Writer credit + 編劇署名 + + + + Penciller credit + 鉛筆畫師署名 + + + + Inker credit + 墨線畫師署名 + + + + Colorist credit + 上色師署名 + + + + Letterer credit + 嵌字師署名 + + + + Cover artist credit + 封面畫師署名 + + + + Editor credit + 編輯署名 + + + + Story arc name + 故事線名稱 + + + + Position within a story arc + 故事線內的位置 + + + + Number of issues in a story arc + 故事線內的期數 + + + + Characters appearing in the comic + 漫畫中出現的角色 + + + + Teams appearing in the comic + 漫畫中出現的團隊 + + + + Locations appearing in the comic + 漫畫中出現的地點 + + + + Primary character or team + 主要角色或團隊 + + + + Comic synopsis + 漫畫簡介 + + + + Publisher name + 出版社名稱 + + + + Publishing imprint + 出版品牌 + + + + Publication format + 出版格式 + + + + Recommended age rating + 建議年齡分級 + + + + Comic genre + 漫畫題材 + + + + ISO language code + ISO 語言代碼 + + + + Publication date metadata + 出版日期元資料 + + + + Series grouping metadata + 系列分組元資料 + + + + Alternate series name + 其他系列名稱 + + + + Alternate issue number + 其他期號 + + + + Alternate series issue count + 其他系列期數 + + + + Number of issues in the series + 系列期數 + + + + Whether the comic is marked as read + 漫畫是否標記為已讀 + + + + Whether reading has started + 是否已開始閱讀 + + + + Whether metadata has been edited + 元資料是否已編輯 + + + + Whether the comic is in color + 漫畫是否為彩色 + + + + Number of pages + 頁數 + + + + Comic file name + 漫畫檔案名稱 + + + + When the item was added + 項目的新增時間 + + + + When the comic was last opened + 漫畫上次開啟時間 + + + + Comic notes + 漫畫備註 + + + + Review text + 評論文字 + + + + Parent folder name + 上層資料夾名稱 + + + + Default reading format for the folder + 資料夾的預設閱讀格式 + + + + Whether the folder is complete + 資料夾是否完整 + + + + Whether the folder is marked as finished + 資料夾是否標記為已完結 + + + + When the folder was updated + 資料夾的更新時間 + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 書庫資料庫遺失。請先還原備份或重新建立書庫,再重新掃描 XML 資訊。 + + + + Unable to load the archive backend: %1 + 無法載入壓縮檔後端:%1 + + + + Unable to start an XML rescan transaction. + 無法啟動 XML 重新掃描交易。 + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 選取的資料夾已不在此書庫中。請重新載入書庫後再掃描。 + + + + Unable to read comics for the XML rescan. + 無法讀取用於 XML 重新掃描的漫畫。 + + + + Unable to commit XML metadata changes. + 無法提交 XML 中繼資料變更。 + + + + Unable to update XML metadata for %1. + 無法更新 %1 的 XML 中繼資料。 diff --git a/YACReaderLibraryServer/yacreaderlibraryserver_zh_TW.ts b/YACReaderLibraryServer/yacreaderlibraryserver_zh_TW.ts index 99def0eb6..f8fed32bc 100644 --- a/YACReaderLibraryServer/yacreaderlibraryserver_zh_TW.ts +++ b/YACReaderLibraryServer/yacreaderlibraryserver_zh_TW.ts @@ -1,28 +1,109 @@ + + DBHelper + + + The folder entry could not be found in the library database. + 在庫資料庫中找不到該資料夾的記錄。 + + FileComic - + CRC error on page (%1): some of the pages will not be displayed correctly - 第 %1 頁 CRC 校驗失敗: 部分頁面將無法正確顯示 + 第 %1 頁 CRC 校驗失敗:部分頁面將無法正確顯示 - + Unknown error opening the file - 打開檔時出現未知錯誤 + 開啟檔案時出現未知錯誤 - + 7z not found 未找到 7z - - + + Format not supported - 不支持的檔格式 + 不支援的檔案格式 + + + + LibraryCreator + + + Unable to create the library data folder: %1. Check folder access and available disk space. + 無法建立書庫資料夾:%1。請檢查資料夾存取權限和可用磁碟空間。 + + + + The library data folder is missing: %1. Restore it or recreate the library before updating. + 書庫資料夾遺失:%1。請先還原該資料夾或重新建立書庫,再進行更新。 + + + + The library database is missing: %1. Restore a backup or recreate the library before updating. + 書庫資料庫遺失:%1。請先還原備份或重新建立書庫,再進行更新。 + + + + + Unable to start a library database transaction: %1 + 無法啟動書庫資料庫交易:%1 + + + + + Unable to commit the library database: %1 + 無法提交書庫資料庫交易:%1 + + + + LibraryMaintenanceLock + + + The library lock owner changed. Retry the operation and review the new owner before recovering it. + 書庫鎖定的擁有者已變更。請重試作業並檢查新的擁有者,然後再恢復存取。 + + + + Unable to remove the maintenance lock: %1. Make sure the file is not read-only and that you can write to the library data folder, then retry. + 無法移除維護鎖定:%1。請確保檔案不是唯讀的,且您可以寫入書庫資料夾,然後重試。 + + + + A folder is occupying the maintenance lock file path: %1. Rename or move that folder before retrying. + 維護鎖定檔案路徑被資料夾佔用:%1。請先重新命名或移動該資料夾,然後重試。 + + + + Unable to create the library lock file: %1. Check write permissions for the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查書庫資料夾的寫入權限。 + + + + The library data folder is missing: %1. Restore the folder or recreate the library before updating it. + 書庫資料夾遺失:%1。請還原該資料夾或重新建立書庫,然後再更新。 + + + + The library has a maintenance lock whose owner could not be verified (%1)%2 + 書庫存在維護鎖定,但無法驗證其擁有者(%1)%2 + + + + Another maintenance operation is using this library (%1)%2 + 另一個維護作業正在使用此書庫(%1)%2 + + + + Unable to create the library lock file: %1. Check available disk space and access to the library data folder. + 無法建立書庫鎖定檔案:%1。請檢查可用磁碟空間和書庫資料夾的存取權限。 @@ -30,15 +111,326 @@ -YACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary. +YACReaderLibraryServer is the headless (no GUI) version of YACReaderLibrary. -This appplication supports persistent settings, to set them up edit this file %1 -To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +This application supports persistent settings. To set them up, edit this file: %1 +To learn about the available settings, please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md -YACReaderLibraryServer 是 YACReaderLibrary 的無頭(無 GUI)版本。 +YACReaderLibraryServer 是 YACReaderLibrary 的 headless(無 GUI)版本。 -此應用程式支援持久性設置,要設定它們,請編輯此文件 %1 -若要了解可用設置,請查看文件:https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md +此應用程式支援持久性設定,要設定它們,請編輯此檔案 %1 +若要了解可用設定,請查看文件:https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md + + + + SearchFieldRegistry + + + Text, quoted text + 文字、加上引號的文字 + + + + Integer + 整數 + + + + Boolean (true / false) + 布林值 (true / false) + + + + Integer (number of days) + 整數(天數) + + + + Enum (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + 列舉 (comic, manga, westernmanga, webcomic/web, 4koma/yonkoma) + + + + Comic title + 漫畫標題 + + + + Series name + 系列名稱 + + + + Issue number + 期號 + + + + Volume identifier + 卷冊識別碼 + + + + Reading format + 閱讀格式 + + + + Comic rating + 漫畫評分 + + + + Textual tags + 文字標籤 + + + + Writer credit + 編劇署名 + + + + Penciller credit + 鉛筆畫師署名 + + + + Inker credit + 墨線畫師署名 + + + + Colorist credit + 上色師署名 + + + + Letterer credit + 嵌字師署名 + + + + Cover artist credit + 封面畫師署名 + + + + Editor credit + 編輯署名 + + + + Story arc name + 故事線名稱 + + + + Position within a story arc + 故事線中的位置 + + + + Number of issues in a story arc + 故事線中的期數 + + + + Characters appearing in the comic + 漫畫中出現的角色 + + + + Teams appearing in the comic + 漫畫中出現的團隊 + + + + Locations appearing in the comic + 漫畫中出現的地點 + + + + Primary character or team + 主要角色或團隊 + + + + Comic synopsis + 漫畫簡介 + + + + Publisher name + 出版社名稱 + + + + Publishing imprint + 出版品牌 + + + + Publication format + 出版格式 + + + + Recommended age rating + 建議年齡分級 + + + + Comic genre + 漫畫題材 + + + + ISO language code + ISO 語言代碼 + + + + Publication date metadata + 出版日期中繼資料 + + + + Series grouping metadata + 系列分組中繼資料 + + + + Alternate series name + 替代系列名稱 + + + + Alternate issue number + 替代期號 + + + + Alternate series issue count + 替代系列期數 + + + + Number of issues in the series + 系列期數 + + + + Whether the comic is marked as read + 漫畫是否標示為已讀 + + + + Whether reading has started + 是否已開始閱讀 + + + + Whether metadata has been edited + 中繼資料是否已編輯 + + + + Whether the comic is in color + 漫畫是否為彩色 + + + + Number of pages + 頁數 + + + + Comic file name + 漫畫檔案名稱 + + + + When the item was added + 項目的加入時間 + + + + When the comic was last opened + 漫畫上次開啟時間 + + + + Comic notes + 漫畫備註 + + + + Review text + 評論文字 + + + + Parent folder name + 上層資料夾名稱 + + + + Default reading format for the folder + 資料夾的預設閱讀格式 + + + + Whether the folder is complete + 資料夾是否完整 + + + + Whether the folder is marked as finished + 資料夾是否標示為已完結 + + + + When the folder was updated + 資料夾的更新時間 + + + + YACReader::XMLInfoLibraryScanner + + + The library database is missing. Restore a backup or recreate the library before rescanning XML information. + 書庫資料庫遺失。請先還原備份或重新建立書庫,再重新掃描 XML 資訊。 + + + + Unable to load the archive backend: %1 + 無法載入壓縮檔後端:%1 + + + + Unable to start an XML rescan transaction. + 無法啟動 XML 重新掃描交易。 + + + + The selected folder is no longer in this library. Reload the library before rescanning. + 選取的資料夾已不在此書庫中。請重新載入書庫後再掃描。 + + + + Unable to read comics for the XML rescan. + 無法讀取用於 XML 重新掃描的漫畫。 + + + + Unable to commit XML metadata changes. + 無法提交 XML 中繼資料變更。 + + + + Unable to update XML metadata for %1. + 無法更新 %1 的 XML 中繼資料。 diff --git a/ci/win/build_installer_qt6.iss b/ci/win/build_installer_qt6.iss index cb2cf00dd..e734e64b6 100644 --- a/ci/win/build_installer_qt6.iss +++ b/ci/win/build_installer_qt6.iss @@ -41,6 +41,7 @@ Root: HKCR; Subkey: YACReader Data Base (ydb)\DefaultIcon; ValueType: string; Va Source: Qt6Widgets.dll; DestDir: {app} Source: Qt6Core.dll; DestDir: {app} Source: Qt6Core5Compat.dll; DestDir: {app} +Source: Qt6Concurrent.dll; DestDir: {app} Source: Qt6Gui.dll; DestDir: {app} Source: Qt6Multimedia.dll; DestDir: {app} Source: Qt6Network.dll; DestDir: {app} diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index b13cf87b6..136e90ab0 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -40,5 +40,9 @@ function(yacreader_apply_build_options) set_property(TARGET "${target_name}" PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) endif() + get_target_property(target_type "${target_name}" TYPE) + if(target_type STREQUAL "EXECUTABLE") + yacreader_deploy_7z_runtime("${target_name}") + endif() endforeach() endfunction() diff --git a/cmake/SevenZipRuntime.cmake b/cmake/SevenZipRuntime.cmake new file mode 100644 index 000000000..f2e45729c --- /dev/null +++ b/cmake/SevenZipRuntime.cmake @@ -0,0 +1,37 @@ +# With the 7zip backend, the apps load the 7z library at runtime from utils/ next to the executable +# (on Linux after the installed paths). Copy the bundled library there, so builds run and tests can read archives. +function(yacreader_deploy_7z_runtime target_name) + if(NOT DECOMPRESSION_BACKEND STREQUAL "7zip") + return() + endif() + + set(dependencies "${CMAKE_SOURCE_DIR}/dependencies/7zip") + if(WIN32) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$") + set(library "${dependencies}/win/arm64/7z.dll") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(library "${dependencies}/win/x64/7z.dll") + else() + set(library "${dependencies}/win/x86/7z.dll") + endif() + elseif(APPLE) + set(library "${dependencies}/macx/universal/7z.so") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$") + set(library "${dependencies}/linux/arm64/7z.so") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$") + set(library "${dependencies}/linux/x86_64/7z.so") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + set(library "${dependencies}/linux/x86/7z.so") + endif() + + if(NOT DEFINED library OR NOT EXISTS "${library}") + message(WARNING "No bundled 7z library for ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}; ${target_name} needs an installed one") + return() + endif() + + get_filename_component(library_name "${library}" NAME) + add_custom_command(TARGET "${target_name}" POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E make_directory "$/utils" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${library}" "$/utils/${library_name}" + VERBATIM) +endfunction() diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 822540bfa..e98d94ff7 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -64,10 +64,17 @@ target_sources(worker INTERFACE add_library(common_all STATIC comic_db.h comic_db.cpp + library_item_types.h + overloaded.h + comic_file.h + comic_file.cpp + library_ids.h + table_projection.h + table_projection.cpp + descriptive_comic_metadata.h + descriptive_comic_metadata.cpp folder.h folder.cpp - library_item.h - library_item.cpp bookmarks.h bookmarks.cpp http_worker.h diff --git a/common/app_language_utils.h b/common/app_language_utils.h index e74968263..b09e8652d 100644 --- a/common/app_language_utils.h +++ b/common/app_language_utils.h @@ -41,12 +41,28 @@ inline bool loadFromLocale(QTranslator &translator, const QString &prefix, const return false; } +// The language picker stores the suffix of an installed file (e.g. pt_PT), so that file is tried first. +// QTranslator::load(QLocale, ...) walks pt_Latn_PT -> pt before trying pt_PT, so a generic pt file would shadow pt_PT +inline bool loadFromLanguageCode(QTranslator &translator, const QString &prefix, const QString &languageCode) +{ + const auto dirs = translationDirectories(); + for (const auto &dir : dirs) { + if (translator.load(prefix + "_" + languageCode, dir)) + return true; + } + + return false; +} + inline bool loadTranslator(QTranslator &translator, const QString &prefix, const QString &languageOverride = QString()) { auto selectedLanguage = languageOverride.trimmed(); if (!selectedLanguage.isEmpty()) { selectedLanguage.replace('-', '_'); + if (loadFromLanguageCode(translator, prefix, selectedLanguage)) + return true; + if (loadFromLocale(translator, prefix, QLocale(selectedLanguage))) return true; diff --git a/common/comic.cpp b/common/comic.cpp index d96c45260..a340b7030 100644 --- a/common/comic.cpp +++ b/common/comic.cpp @@ -287,6 +287,17 @@ void Comic::invalidate() emit invalidated(); } //----------------------------------------------------------------------------- +void Comic::resolveFirstPage() +{ + if (_firstPage == -1) { + _firstPage = bm->getLastPage(); + } + + if (_firstPage < 0 || _firstPage >= _pages.size()) { + _firstPage = 0; + } +} +//----------------------------------------------------------------------------- QByteArray Comic::getRawPage(int page) const { if (page < 0 || page >= _pages.size()) { @@ -414,10 +425,11 @@ bool FileComic::load(const QString &path, const ComicDB &comic) if (fi.exists()) { QList bookmarkIndexes; bookmarkIndexes << comic.info.bookmark1 << comic.info.bookmark2 << comic.info.bookmark3; - if (bm->load(bookmarkIndexes, comic.info.currentPage - 1)) { + const int lastReadPage = std::max(comic.info.currentPage - 1, 0); + if (bm->load(bookmarkIndexes, lastReadPage)) { emit bookmarksUpdated(); } - _firstPage = comic.info.currentPage - 1; + _firstPage = lastReadPage; _path = QDir::cleanPath(path); return true; } else { @@ -657,13 +669,7 @@ void FileComic::process() _archiveIndexToPages[_pageArchiveIndexes.at(page)].append(page); } - if (_firstPage == -1) { - _firstPage = bm->getLastPage(); - } - - if (_firstPage >= _pages.length()) { - _firstPage = 0; - } + resolveFirstPage(); _index = _firstPage; emit openAt(_index); @@ -743,13 +749,7 @@ void FolderComic::process() moveToThread(QCoreApplication::instance()->thread()); emit errorOpening(); } else { - if (_firstPage == -1) { - _firstPage = bm->getLastPage(); - } - - if (_firstPage >= _pages.length()) { - _firstPage = 0; - } + resolveFirstPage(); _index = _firstPage; @@ -831,10 +831,11 @@ bool PDFComic::load(const QString &path, const ComicDB &comic) if (fi.exists()) { QList bookmarkIndexes; bookmarkIndexes << comic.info.bookmark1 << comic.info.bookmark2 << comic.info.bookmark3; - if (bm->load(bookmarkIndexes, comic.info.currentPage - 1)) { + const int lastReadPage = std::max(comic.info.currentPage - 1, 0); + if (bm->load(bookmarkIndexes, lastReadPage)) { emit bookmarksUpdated(); } - _firstPage = comic.info.currentPage - 1; + _firstPage = lastReadPage; _path = QDir::cleanPath(path); return true; } else { @@ -887,13 +888,7 @@ void PDFComic::process() _pages.resize(nPages); _loadedPages = QVector(nPages, false); - if (_firstPage == -1) { - _firstPage = bm->getLastPage(); - } - - if (_firstPage >= _pages.length()) { - _firstPage = 0; - } + resolveFirstPage(); _index = _firstPage; emit openAt(_index); diff --git a/common/comic.h b/common/comic.h index d18b2a395..87469cf65 100644 --- a/common/comic.h +++ b/common/comic.h @@ -34,7 +34,7 @@ class Comic : public QObject bool _loaded; // open the comic at this point - int _firstPage; + int _firstPage = -1; bool _isPDF; @@ -42,6 +42,8 @@ class Comic : public QObject bool _errorOpening; + void resolveFirstPage(); + public: static const QStringList imageExtensions; static const QStringList literalImageExtensions; diff --git a/common/comic_db.cpp b/common/comic_db.cpp index 04a494712..5db6c041d 100644 --- a/common/comic_db.cpp +++ b/common/comic_db.cpp @@ -1,35 +1,24 @@ #include "comic_db.h" +#include "table_projection.h" #include "yacreader_global.h" #include +#include +#include #include //----------------------------------------------------------------------------- // COMIC------------------------------------------------------------------------ //----------------------------------------------------------------------------- -ComicDB::ComicDB() -{ -} - -ComicDB::ComicDB(const ComicDB &comicDB) -{ - operator=(comicDB); -} - -bool ComicDB::isDir() const -{ - return false; -} - -QString ComicDB::toTXT() +QString ComicDB::toTXT() const { QString txt; // Legacy info - txt.append(QString("comicid:%1\r\n").arg(id)); + txt.append(QString("comicid:%1\r\n").arg(file.id.value())); txt.append(QString("hash:%1\r\n").arg(info.hash)); - txt.append(QString("path:%1\r\n").arg(path)); + txt.append(QString("path:%1\r\n").arg(file.path)); txt.append(QString("numpages:%1\r\n").arg(info.numPages.toString())); // new 7.0 @@ -186,81 +175,88 @@ QString ComicDB::toTXT() return txt; } -ComicDB &ComicDB::operator=(const ComicDB &other) +QString ComicDB::getFileName() const { - LibraryItem::operator=(other); - - this->_hasCover = other._hasCover; - - this->info = other.info; + return QFileInfo(file.path).fileName(); +} - return *this; +QString ComicDB::getTitleOrFileName() const +{ + return titleOrFileName(info, QFileInfo(file.path).fileName()); } -QString ComicDB::getFileName() const +qulonglong ComicDB::getFileSize() const { - return QFileInfo(path).fileName(); + // the size is encoded in the hash after the SHA-1 + return info.hash.right(info.hash.length() - 40).toLongLong(); } -QString ComicDB::getTitleOrFileName() const +QString ComicDB::getTitleIncludingNumber() const { - if (!info.title.isNull() && !info.title.toString().isEmpty()) - return info.title.toString(); - else - return QFileInfo(path).fileName(); + return titleIncludingNumber(info, QFileInfo(file.path).fileName()); } -QString ComicDB::getParentFolderName() const +QString ComicDB::getInfoTitle() const { - QStringList paths = path.split('/'); - if (paths.length() < 2) - return ""; - else - return paths[paths.length() - 2]; + return infoTitle(info, QFileInfo(file.path).fileName()); } -qulonglong ComicDB::getFileSize() const +static bool hasText(const QVariant &value) { - // the size is encoded in the hash after the SHA-1 - return info.hash.right(info.hash.length() - 40).toLongLong(); + return !value.isNull() && !value.toString().trimmed().isEmpty(); } -QString ComicDB::getTitleIncludingNumber() const +QString titleOrFileName(const DescriptiveComicMetadata &info, const QString &fileName) { - if (!info.number.isNull()) { - return "#" + info.number.toString() + " - " + getTitleOrFileName(); + if (hasText(info.title)) + return info.title.toString(); + if (hasText(info.series)) + return info.series.toString(); + if (hasText(info.volume)) + return info.volume.toString(); + return fileName.trimmed().isEmpty() ? QString() : fileName; +} + +QString titleIncludingNumber(const DescriptiveComicMetadata &info, const QString &fileName) +{ + const auto title = titleOrFileName(info, fileName); + // Standalone labels need an issue number. Preserve filename fallbacks as-is: + // they often already contain the number, and must remain recognizable. + if (hasText(info.number)) { + if (!hasText(info.title) && hasText(info.series)) + return title + " #" + info.number.toString(); + if (hasText(info.title) || hasText(info.volume)) + return "#" + info.number.toString() + " - " + title; + if (title.isEmpty()) + return "#" + info.number.toString(); } - - return getTitleOrFileName(); + return title; } -QString ComicDB::getInfoTitle() const +QString infoTitle(const DescriptiveComicMetadata &info, const QString &fileName) { - if (!info.number.isNull() && !info.title.isNull() && !info.series.isNull()) + if (hasText(info.number) && hasText(info.title) && hasText(info.series)) return "#" + info.number.toString() + " - " + info.title.toString() + " (" + info.series.toString() + ")"; - if (!info.title.isNull() && !info.series.isNull()) + if (hasText(info.title) && hasText(info.series)) return info.title.toString() + " (" + info.series.toString() + ")"; - if (!info.number.isNull() && !info.title.isNull()) + if (hasText(info.number) && hasText(info.title)) return "#" + info.number.toString() + " - " + info.title.toString(); - if (!info.number.isNull() && !info.series.isNull()) + if (hasText(info.number) && hasText(info.series)) return "#" + info.number.toString() + " - " + info.series.toString(); - if (!info.number.isNull()) - return "#" + info.number.toString() + " - " + getTitleOrFileName(); - - if (!info.title.isNull() && !info.series.isNull()) - return info.title.toString() + " (" + info.series.toString() + ")"; - - if (!info.title.isNull()) - return info.title.toString(); - - if (!info.series.isNull()) - return info.series.toString(); + return titleIncludingNumber(info, fileName); +} - return QFileInfo(path).fileName(); +QString parentFolderName(const QString &path) +{ + QStringList paths = path.split('/'); + if (paths.length() < 2) + return ""; + else + return paths[paths.length() - 2]; } //----------------------------------------------------------------------------- @@ -269,7 +265,6 @@ QString ComicDB::getInfoTitle() const ComicInfo::ComicInfo() : read(false), edited(false), - existOnDb(false), rating(0), hasBeenOpened(false), currentPage(1), @@ -282,16 +277,6 @@ ComicInfo::ComicInfo() { } -ComicInfo::ComicInfo(const ComicInfo &comicInfo) - : QObject() -{ - operator=(comicInfo); -} - -ComicInfo::~ComicInfo() -{ -} - void ComicInfo::deleteMetadata() { title = QVariant(); @@ -318,10 +303,6 @@ void ComicInfo::deleteMetadata() date = QVariant(); - day = QVariant(); - month = QVariant(); - year = QVariant(); - publisher = QVariant(); format = QVariant(); color = QVariant(); @@ -349,196 +330,12 @@ void ComicInfo::deleteMetadata() comicVineID = QVariant(); } -// the default operator= should work -ComicInfo &ComicInfo::operator=(const ComicInfo &comicInfo) -{ - hash = comicInfo.hash; - id = comicInfo.id; - existOnDb = comicInfo.existOnDb; - read = comicInfo.read; - edited = comicInfo.edited; - - hasBeenOpened = comicInfo.hasBeenOpened; - rating = comicInfo.rating; - currentPage = comicInfo.currentPage; - bookmark1 = comicInfo.bookmark1; - bookmark2 = comicInfo.bookmark2; - bookmark3 = comicInfo.bookmark3; - brightness = comicInfo.brightness; - contrast = comicInfo.contrast; - gamma = comicInfo.gamma; - - title = comicInfo.title; - coverPage = comicInfo.coverPage; - numPages = comicInfo.numPages; - number = comicInfo.number; - isBis = comicInfo.isBis; - count = comicInfo.count; - volume = comicInfo.volume; - storyArc = comicInfo.storyArc; - arcNumber = comicInfo.arcNumber; - arcCount = comicInfo.arcCount; - genere = comicInfo.genere; - writer = comicInfo.writer; - penciller = comicInfo.penciller; - inker = comicInfo.inker; - colorist = comicInfo.colorist; - letterer = comicInfo.letterer; - coverArtist = comicInfo.coverArtist; - date = comicInfo.date; - publisher = comicInfo.publisher; - format = comicInfo.format; - color = comicInfo.color; - ageRating = comicInfo.ageRating; - synopsis = comicInfo.synopsis; - characters = comicInfo.characters; - notes = comicInfo.notes; - comicVineID = comicInfo.comicVineID; - - lastTimeOpened = comicInfo.lastTimeOpened; - - coverSizeRatio = comicInfo.coverSizeRatio; - originalCoverSize = comicInfo.originalCoverSize; - - added = comicInfo.added; - type = comicInfo.type; - editor = comicInfo.editor; - imprint = comicInfo.imprint; - teams = comicInfo.teams; - locations = comicInfo.locations; - series = comicInfo.series; - alternateSeries = comicInfo.alternateSeries; - alternateNumber = comicInfo.alternateNumber; - alternateCount = comicInfo.alternateCount; - languageISO = comicInfo.languageISO; - seriesGroup = comicInfo.seriesGroup; - mainCharacterOrTeam = comicInfo.mainCharacterOrTeam; - review = comicInfo.review; - tags = comicInfo.tags; - - imageFiltersJson = comicInfo.imageFiltersJson; - lastTimeImageFiltersSet = comicInfo.lastTimeImageFiltersSet; - lastTimeCoverSet = comicInfo.lastTimeCoverSet; - usesExternalCover = comicInfo.usesExternalCover; - lastTimeMetadataSet = comicInfo.lastTimeMetadataSet; - - return *this; -} - -QPixmap ComicInfo::getCover(const QString &basePath) +QPixmap ComicInfo::getCover(const QString &basePath) const { return QPixmap(YACReader::LibraryPaths::coverPath(basePath, hash)); } -QStringList ComicInfo::getWriters() -{ - if (writer.toString().length() > 0) { - return writer.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getPencillers() -{ - if (penciller.toString().length() > 0) { - return penciller.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getInkers() -{ - if (inker.toString().length() > 0) { - return inker.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getColorists() -{ - if (colorist.toString().length() > 0) { - return colorist.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getLetterers() -{ - if (letterer.toString().length() > 0) { - return letterer.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getCoverArtists() -{ - if (coverArtist.toString().length() > 0) { - return coverArtist.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getEditors() -{ - if (editor.toString().length() > 0) { - return editor.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getImprint() -{ - if (imprint.toString().length() > 0) { - return imprint.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getCharacters() -{ - if (characters.toString().length() > 0) { - return characters.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getTeams() -{ - if (teams.toString().length() > 0) { - return teams.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getLocations() -{ - if (locations.toString().length() > 0) { - return locations.toString().split("\n"); - } - - return QStringList(); -} - -QStringList ComicInfo::getTags() -{ - if (tags.toString().length() > 0) { - return tags.toString().split("\n"); - } - - return QStringList(); -} - -QString ComicInfo::getTypeString() +QString ComicInfo::getTypeString() const { if (type.isNull()) { return QStringLiteral("Null"); @@ -566,85 +363,158 @@ QString ComicInfo::getTypeString() return QStringLiteral("Unknown"); } -QString ComicInfo::getStoryArcInfoString() +QString ComicDB::selectList(const QString &fileAlias, const QString &infoAlias) { - if (arcNumber.toString().length() > 0 && arcCount.toString().length() > 0 && storyArc.toString().length() > 0) { - return "(" + arcNumber.toString() + "/" + arcCount.toString() + ") " + storyArc.toString(); - } + return TableProjection::selectList(fileAlias, ComicFile::columns()) + ", " + TableProjection::selectList(infoAlias, ComicInfo::columns()); +} - if (arcNumber.toString().length() > 0 && storyArc.toString().length() > 0) { - return "(" + arcNumber.toString() + ") " + storyArc.toString(); - } +ComicDB::ColumnIndexes ComicDB::columnIndexes(const QSqlRecord &record, const QString &fileAlias, const QString &infoAlias) +{ + return { TableProjection::columnIndexes(record, fileAlias, ComicFile::columns()), TableProjection::columnIndexes(record, infoAlias, ComicInfo::columns()) }; +} - return storyArc.toString().length() > 0 ? storyArc.toString() : ""; +ComicDB ComicDB::fromRecord(const QSqlRecord &record, const QString &fileAlias, const QString &infoAlias) +{ + ComicDB comic; + comic.file = ComicFile::fromRecord(record, fileAlias); + comic.info = ComicInfo::fromRecord(record, infoAlias); + return comic; } -QString ComicInfo::getAlternateSeriesString() +ComicDB ComicDB::fromQuery(const QSqlQuery &query, const ColumnIndexes &columnIndexes) { - if (alternateNumber.toString().length() > 0 && alternateCount.toString().length() > 0 && alternateSeries.toString().length() > 0) { - return "(" + alternateNumber.toString() + "/" + alternateCount.toString() + ") " + alternateSeries.toString(); + ComicDB comic; + comic.file = ComicFile::fromQuery(query, columnIndexes.file); + comic.info = ComicInfo::fromQuery(query, columnIndexes.info); + return comic; +} + +const QStringList &ComicInfo::columns() +{ + static const QStringList columns = [] { + QStringList result = { "hash", "edited", "read", "hasBeenOpened", "rating", "currentPage", + "bookmark1", "bookmark2", "bookmark3", "brightness", "contrast", "gamma", + "coverPage", "numPages", "lastTimeOpened", "coverSizeRatio", "originalCoverSize", + "added", "type", "imageFiltersJson", "lastTimeImageFiltersSet", "lastTimeCoverSet", + "usesExternalCover", "lastTimeMetadataSet" }; + for (const auto &column : DescriptiveComicMetadata::columns()) + result.append(QString::fromLatin1(column.name)); + return result; + }(); + return columns; +} + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +bool ComicInfo::hasSameValues(const ComicInfo &other) const +{ + for (const auto &column : DescriptiveComicMetadata::columns()) { + if (this->*column.member != other.*column.member) + return false; } - if (alternateNumber.toString().length() > 0 && alternateSeries.toString().length() > 0) { - return "(" + alternateNumber.toString() + ") " + alternateSeries.toString(); + return id == other.id && hash == other.hash && read == other.read && edited == other.edited && + hasBeenOpened == other.hasBeenOpened && rating == other.rating && currentPage == other.currentPage && + bookmark1 == other.bookmark1 && bookmark2 == other.bookmark2 && bookmark3 == other.bookmark3 && + brightness == other.brightness && contrast == other.contrast && gamma == other.gamma && + coverPage == other.coverPage && numPages == other.numPages && lastTimeOpened == other.lastTimeOpened && + coverSizeRatio == other.coverSizeRatio && originalCoverSize == other.originalCoverSize && + added == other.added && type == other.type && imageFiltersJson == other.imageFiltersJson && + lastTimeImageFiltersSet == other.lastTimeImageFiltersSet && lastTimeCoverSet == other.lastTimeCoverSet && + usesExternalCover == other.usesExternalCover && lastTimeMetadataSet == other.lastTimeMetadataSet; +} + +namespace { + +// `value(k)` is column k of {id} + ComicInfo::columns() +template +ComicInfo readComicInfo(const Value &value) +{ + ComicInfo info; + + info.id = ComicInfoId(value(0).toULongLong()); + info.hash = value(1).toString(); + info.edited = value(2).toBool(); + info.read = value(3).toBool(); + info.hasBeenOpened = value(4).toBool(); + info.rating = value(5).toInt(); + info.currentPage = value(6).toInt(); + info.bookmark1 = value(7).toInt(); + info.bookmark2 = value(8).toInt(); + info.bookmark3 = value(9).toInt(); + info.brightness = value(10).toInt(); + info.contrast = value(11).toInt(); + info.gamma = value(12).toInt(); + info.coverPage = value(13); + info.numPages = value(14); + info.lastTimeOpened = value(15); + info.coverSizeRatio = value(16); + info.originalCoverSize = value(17); + info.added = value(18); + info.type = value(19); + info.imageFiltersJson = value(20); + info.lastTimeImageFiltersSet = value(21); + info.lastTimeCoverSet = value(22); + info.usesExternalCover = value(23).toBool(); + info.lastTimeMetadataSet = value(24); + + constexpr int firstDescriptiveColumn = 25; + int column = firstDescriptiveColumn; + for (const auto &descriptive : DescriptiveComicMetadata::columns()) { + info.*descriptive.member = value(column++); } - return alternateSeries.toString().length() > 0 ? alternateSeries.toString() : ""; + return info; } -void ComicInfo::setRead(bool r) -{ - if (r != read) { - read = r; - emit readChanged(); - } } -void ComicInfo::setRating(int r) +ComicInfo ComicInfo::fromRecord(const QSqlRecord &record, const QString &alias) { - if (r != rating) { - rating = r; - emit ratingChanged(); - } + const auto indexes = TableProjection::columnIndexes(record, alias, columns()); + return readComicInfo([&](int column) { return record.value(indexes.at(column)); }); } -void ComicInfo::setFavorite(bool f) +ComicInfo ComicInfo::fromQuery(const QSqlQuery &query, const QList &columnIndexes) { - if (f != isFavorite) { - isFavorite = f; - emit favoriteChanged(); - } + return readComicInfo([&](int column) { return query.value(columnIndexes.at(column)); }); } +QT_WARNING_POP QDataStream &operator<<(QDataStream &stream, const ComicDB &comic) { - stream << comic.id; - stream << comic.name; - stream << comic.parentId; - stream << comic.path; - stream << comic._hasCover; + stream << comic.file.id.value(); + stream << comic.file.fileName; + stream << comic.file.folderId.value(); + stream << comic.file.path; + stream << (comic.info.id != ComicInfoId()); // hasCover stream << comic.info; return stream; } QDataStream &operator>>(QDataStream &stream, ComicDB &comic) { - stream >> comic.id; - stream >> comic.name; - stream >> comic.parentId; - stream >> comic.path; - stream >> comic._hasCover; + qulonglong id; + qulonglong folderId; + bool hasCover; + stream >> id; + stream >> comic.file.fileName; + stream >> folderId; + stream >> comic.file.path; + stream >> hasCover; stream >> comic.info; + comic.file.id = ComicId(id); + comic.file.folderId = FolderId(folderId); return stream; } QDataStream &operator<<(QDataStream &stream, const ComicInfo &comicInfo) { - stream << comicInfo.id; + stream << comicInfo.id.value(); stream << comicInfo.read; stream << comicInfo.edited; stream << comicInfo.hash; - stream << comicInfo.existOnDb; + stream << (comicInfo.id != ComicInfoId()); // existOnDb stream << comicInfo.hasBeenOpened; stream << comicInfo.rating; @@ -725,11 +595,14 @@ QDataStream &operator<<(QDataStream &stream, const ComicInfo &comicInfo) QDataStream &operator>>(QDataStream &stream, ComicInfo &comicInfo) { - stream >> comicInfo.id; + qulonglong id; + stream >> id; + comicInfo.id = ComicInfoId(id); stream >> comicInfo.read; stream >> comicInfo.edited; stream >> comicInfo.hash; - stream >> comicInfo.existOnDb; + bool existOnDb; + stream >> existOnDb; stream >> comicInfo.hasBeenOpened; stream >> comicInfo.rating; diff --git a/common/comic_db.h b/common/comic_db.h index c72a01ce3..051e5c1c9 100644 --- a/common/comic_db.h +++ b/common/comic_db.h @@ -1,7 +1,9 @@ #ifndef __COMICDB_H #define __COMICDB_H -#include "library_item.h" +#include "comic_file.h" +#include "descriptive_comic_metadata.h" +#include "library_ids.h" #include #include @@ -9,27 +11,24 @@ #include #include -class ComicInfo : public QObject +class ComicInfo : public DescriptiveComicMetadata { - Q_OBJECT public: ComicInfo(); - ComicInfo(const ComicInfo &comicInfo); - ~ComicInfo(); void deleteMetadata(); - ComicInfo &operator=(const ComicInfo &comicInfo); + bool operator==(const ComicInfo &other) const { return id == other.id; } + bool operator!=(const ComicInfo &other) const { return id != other.id; } - bool operator==(const ComicInfo &other) { return id == other.id; } - bool operator!=(const ComicInfo &other) { return id != other.id; } + // operator== compares only the id; this compares every column + bool hasSameValues(const ComicInfo &other) const; // mandatory fields - qulonglong id; + ComicInfoId id; bool read; bool edited; QString hash; - bool existOnDb; int rating; // TODO_METADATA: change to float @@ -45,65 +44,15 @@ class ComicInfo : public QObject [[deprecated("Use imageFiltersJson")]] int gamma; //----------------- - QVariant title; // string - QVariant coverPage; // int QVariant numPages; // int - QVariant number; // string (changed in 9.13 from int) - QVariant isBis; // bool - QVariant count; // int - - QVariant volume; // string - QVariant storyArc; // string - QVariant arcNumber; // string (changed in 9.13 from int) - QVariant arcCount; // int - - QVariant genere; // string - - QVariant writer; // string - QVariant penciller; // string - QVariant inker; // string - QVariant colorist; // string - QVariant letterer; // string - QVariant coverArtist; // string - - QVariant date; // string - - QVariant day; - QVariant month; - QVariant year; - - QVariant publisher; // string - QVariant format; // string - QVariant color; // bool - QVariant ageRating; // string - - QVariant synopsis; // string - QVariant characters; // string - QVariant notes; // string - - QVariant comicVineID; // string - QVariant lastTimeOpened; // integer/date QVariant coverSizeRatio; // h/w QVariant originalCoverSize; // string "WxH" QVariant added; // integer/date QVariant type; // enum - QVariant editor; // string - QVariant imprint; // string - QVariant teams; // string/list - QVariant locations; // string/list - QVariant series; // string - QVariant alternateSeries; // string - QVariant alternateNumber; // string - QVariant alternateCount; // int - QVariant languageISO; // string - QVariant seriesGroup; // string - QVariant mainCharacterOrTeam; // string - QVariant review; // string - QVariant tags; // string/list QVariant imageFiltersJson; // string, JSON with image filters QVariant lastTimeImageFiltersSet; // integer/date, last time image filters were set @@ -111,167 +60,52 @@ class ComicInfo : public QObject QVariant usesExternalCover; // bool, whether the cover is external or not QVariant lastTimeMetadataSet; // integer/date, last time metadata was set - QPixmap getCover(const QString &basePath); - - Q_INVOKABLE QStringList getWriters(); - Q_INVOKABLE QStringList getPencillers(); - Q_INVOKABLE QStringList getInkers(); - Q_INVOKABLE QStringList getColorists(); - Q_INVOKABLE QStringList getLetterers(); - Q_INVOKABLE QStringList getCoverArtists(); - Q_INVOKABLE QStringList getEditors(); - Q_INVOKABLE QStringList getImprint(); + QPixmap getCover(const QString &basePath) const; - Q_INVOKABLE QStringList getCharacters(); + QString getTypeString() const; - Q_INVOKABLE QStringList getTeams(); - Q_INVOKABLE QStringList getLocations(); - - Q_INVOKABLE QStringList getTags(); - - Q_INVOKABLE QString getTypeString(); - - Q_INVOKABLE QString getStoryArcInfoString(); - Q_INVOKABLE QString getAlternateSeriesString(); + static const QStringList &columns(); + static ComicInfo fromRecord(const QSqlRecord &record, const QString &alias); + static ComicInfo fromQuery(const QSqlQuery &query, const QList &columnIndexes); friend QDataStream &operator<<(QDataStream &stream, const ComicInfo &comicInfo); friend QDataStream &operator>>(QDataStream &stream, ComicInfo &comicInfo); - - Q_PROPERTY(qulonglong id MEMBER id CONSTANT) - Q_PROPERTY(bool read MEMBER read WRITE setRead NOTIFY readChanged) - Q_PROPERTY(bool edited MEMBER edited CONSTANT) - Q_PROPERTY(QString hash MEMBER hash CONSTANT) - Q_PROPERTY(bool existOnDb MEMBER existOnDb CONSTANT) - - Q_PROPERTY(int rating MEMBER rating WRITE setRating NOTIFY ratingChanged) - - Q_PROPERTY(bool hasBeenOpened MEMBER hasBeenOpened CONSTANT) - - Q_PROPERTY(int currentPage MEMBER currentPage CONSTANT) - Q_PROPERTY(int bookmark1 MEMBER bookmark1 CONSTANT) - Q_PROPERTY(int bookmark2 MEMBER bookmark2 CONSTANT) - Q_PROPERTY(int bookmark3 MEMBER bookmark3 CONSTANT) - Q_PROPERTY(int brightness MEMBER brightness CONSTANT) - Q_PROPERTY(int contrast MEMBER contrast CONSTANT) - Q_PROPERTY(int gamma MEMBER gamma CONSTANT) - - Q_PROPERTY(QVariant title MEMBER title CONSTANT) - - Q_PROPERTY(QVariant coverPage MEMBER coverPage CONSTANT) - Q_PROPERTY(QVariant numPages MEMBER numPages CONSTANT) - - Q_PROPERTY(QVariant number MEMBER number CONSTANT) - Q_PROPERTY(QVariant isBis MEMBER isBis CONSTANT) - Q_PROPERTY(QVariant count MEMBER count CONSTANT) - - Q_PROPERTY(QVariant volume MEMBER volume CONSTANT) - Q_PROPERTY(QVariant storyArc MEMBER storyArc CONSTANT) - Q_PROPERTY(QVariant arcNumber MEMBER arcNumber CONSTANT) - Q_PROPERTY(QVariant arcCount MEMBER arcCount CONSTANT) - - Q_PROPERTY(QVariant genere MEMBER genere CONSTANT) - - Q_PROPERTY(QVariant writer MEMBER writer CONSTANT) - Q_PROPERTY(QVariant penciller MEMBER penciller CONSTANT) - Q_PROPERTY(QVariant inker MEMBER inker CONSTANT) - Q_PROPERTY(QVariant colorist MEMBER colorist CONSTANT) - Q_PROPERTY(QVariant letterer MEMBER letterer CONSTANT) - Q_PROPERTY(QVariant coverArtist MEMBER coverArtist CONSTANT) - - Q_PROPERTY(QVariant date MEMBER date CONSTANT) - Q_PROPERTY(QVariant day MEMBER day CONSTANT) - Q_PROPERTY(QVariant month MEMBER month CONSTANT) - Q_PROPERTY(QVariant year MEMBER year CONSTANT) - Q_PROPERTY(QVariant publisher MEMBER publisher CONSTANT) - Q_PROPERTY(QVariant format MEMBER format CONSTANT) - Q_PROPERTY(QVariant color MEMBER color CONSTANT) - Q_PROPERTY(QVariant ageRating MEMBER ageRating CONSTANT) - - Q_PROPERTY(QVariant synopsis MEMBER synopsis CONSTANT) - Q_PROPERTY(QVariant characters MEMBER characters CONSTANT) - Q_PROPERTY(QVariant notes MEMBER notes CONSTANT) - - Q_PROPERTY(QVariant comicVineID MEMBER comicVineID CONSTANT) - - Q_PROPERTY(QVariant lastTimeOpened MEMBER lastTimeOpened CONSTANT) - - Q_PROPERTY(QVariant coverSizeRatio MEMBER coverSizeRatio CONSTANT) - Q_PROPERTY(QVariant originalCoverSize MEMBER originalCoverSize CONSTANT) - - Q_PROPERTY(QVariant added MEMBER added CONSTANT) - Q_PROPERTY(QVariant type MEMBER type CONSTANT) - Q_PROPERTY(QVariant editor MEMBER editor CONSTANT) - Q_PROPERTY(QVariant imprint MEMBER imprint CONSTANT) - Q_PROPERTY(QVariant teams MEMBER teams CONSTANT) - Q_PROPERTY(QVariant locations MEMBER locations CONSTANT) - Q_PROPERTY(QVariant series MEMBER series CONSTANT) - Q_PROPERTY(QVariant alternateSeries MEMBER alternateSeries CONSTANT) - Q_PROPERTY(QVariant alternateNumber MEMBER alternateNumber CONSTANT) - Q_PROPERTY(QVariant alternateCount MEMBER alternateCount CONSTANT) - Q_PROPERTY(QVariant languageISO MEMBER languageISO CONSTANT) - Q_PROPERTY(QVariant seriesGroup MEMBER seriesGroup CONSTANT) - Q_PROPERTY(QVariant mainCharacterOrTeam MEMBER mainCharacterOrTeam CONSTANT) - Q_PROPERTY(QVariant review MEMBER review CONSTANT) - Q_PROPERTY(QVariant tags MEMBER tags CONSTANT) - - Q_PROPERTY(QVariant imageFiltersJson MEMBER imageFiltersJson CONSTANT) - Q_PROPERTY(QVariant lastTimeImageFiltersSet MEMBER lastTimeImageFiltersSet CONSTANT) - Q_PROPERTY(QVariant lastTimeCoverSet MEMBER lastTimeCoverSet CONSTANT) - Q_PROPERTY(QVariant usesExternalCover MEMBER usesExternalCover CONSTANT) - Q_PROPERTY(QVariant lastTimeMetadataSet MEMBER lastTimeMetadataSet CONSTANT) - - //-new properties, not loaded from the DB automatically - bool isFavorite; - Q_PROPERTY(bool isFavorite MEMBER isFavorite WRITE setFavorite NOTIFY favoriteChanged) - - // setters, used in QML only for now - void setRead(bool r); - void setRating(int r); - void setFavorite(bool f); - -private: -signals: - void readChanged(); - void ratingChanged(); - void favoriteChanged(); }; -class ComicDB : public LibraryItem +class ComicDB { - Q_OBJECT public: - ComicDB(); - ComicDB(const ComicDB &comicDB); - - bool isDir() const; - - bool _hasCover; - - bool hasCover() { return _hasCover; } + ComicFile file; + ComicInfo info; // return comic file name QString getFileName() const; // returns comic title if it isn't null or empty, in other case returns fileName - Q_INVOKABLE QString getTitleOrFileName() const; + QString getTitleOrFileName() const; // returns parent folder name - QString getParentFolderName() const; // return the size of the file in bytes - Q_INVOKABLE qulonglong getFileSize() const; + qulonglong getFileSize() const; - Q_INVOKABLE QString getTitleIncludingNumber() const; - Q_INVOKABLE QString getInfoTitle() const; + QString getTitleIncludingNumber() const; + QString getInfoTitle() const; - QString toTXT(); + QString toTXT() const; - ComicInfo info; - Q_PROPERTY(ComicInfo info MEMBER info) + bool operator==(const ComicDB &other) const { return file.id == other.file.id; } - ComicDB &operator=(const ComicDB &other); - bool operator==(const ComicDB &other) const { return id == other.id; } + struct ColumnIndexes { + QList file; + QList info; + }; + + static QString selectList(const QString &fileAlias, const QString &infoAlias); + static ColumnIndexes columnIndexes(const QSqlRecord &record, const QString &fileAlias, const QString &infoAlias); + static ComicDB fromRecord(const QSqlRecord &record, const QString &fileAlias, const QString &infoAlias); + static ComicDB fromQuery(const QSqlQuery &query, const ColumnIndexes &columnIndexes); friend QDataStream &operator<<(QDataStream &, const ComicDB &); friend QDataStream &operator>>(QDataStream &, ComicDB &); @@ -279,4 +113,12 @@ class ComicDB : public LibraryItem Q_DECLARE_METATYPE(ComicDB) +// the titles of ComicDB, also for a comic without a file. Without a title: "Series #Number", the series, or "#Number"; +// else the file name; empty when there is nothing +QString titleOrFileName(const DescriptiveComicMetadata &info, const QString &fileName); +QString titleIncludingNumber(const DescriptiveComicMetadata &info, const QString &fileName); +QString infoTitle(const DescriptiveComicMetadata &info, const QString &fileName); +// the name of the folder that holds `path` (a path in the library, with '/') +QString parentFolderName(const QString &path); + #endif diff --git a/common/comic_file.cpp b/common/comic_file.cpp new file mode 100644 index 000000000..56440533c --- /dev/null +++ b/common/comic_file.cpp @@ -0,0 +1,48 @@ +#include "comic_file.h" + +#include "table_projection.h" + +#include +#include + +const QStringList &ComicFile::columns() +{ + static const QStringList columns = { "parentId", "comicInfoId", "fileName", "path" }; + return columns; +} + +namespace { + +// `value(k)` is column k of {id} + ComicFile::columns() +template +ComicFile readComicFile(const Value &value) +{ + ComicFile file; + file.id = ComicId(value(0).toULongLong()); + file.folderId = FolderId(value(1).toULongLong()); + file.comicInfoId = ComicInfoId(value(2).toULongLong()); + file.fileName = value(3).toString(); + file.path = value(4).toString(); + return file; +} + +} + +ComicFile ComicFile::fromRecord(const QSqlRecord &record, const QString &alias) +{ + const auto indexes = TableProjection::columnIndexes(record, alias, columns()); + return readComicFile([&](int column) { return record.value(indexes.at(column)); }); +} + +ComicFile ComicFile::fromQuery(const QSqlQuery &query, const QList &columnIndexes) +{ + return readComicFile([&](int column) { return query.value(columnIndexes.at(column)); }); +} + +void ComicFile::bindTo(QSqlQuery &query) const +{ + query.bindValue(":parentId", folderId.value()); + query.bindValue(":comicInfoId", comicInfoId.value()); + query.bindValue(":fileName", fileName); + query.bindValue(":path", path); +} diff --git a/common/comic_file.h b/common/comic_file.h new file mode 100644 index 000000000..baa8b3547 --- /dev/null +++ b/common/comic_file.h @@ -0,0 +1,27 @@ +#ifndef COMIC_FILE_H +#define COMIC_FILE_H + +#include "library_ids.h" + +#include +#include + +class QSqlQuery; +class QSqlRecord; + +struct ComicFile { + ComicId id; + FolderId folderId; // column parentId + ComicInfoId comicInfoId; + QString fileName; + QString path; + + friend bool operator==(const ComicFile &, const ComicFile &) = default; + + static const QStringList &columns(); + static ComicFile fromRecord(const QSqlRecord &record, const QString &alias); + static ComicFile fromQuery(const QSqlQuery &query, const QList &columnIndexes); + void bindTo(QSqlQuery &query) const; +}; + +#endif diff --git a/common/descriptive_comic_metadata.cpp b/common/descriptive_comic_metadata.cpp new file mode 100644 index 000000000..8dd90df7e --- /dev/null +++ b/common/descriptive_comic_metadata.cpp @@ -0,0 +1,261 @@ +#include "descriptive_comic_metadata.h" + +#include +#include + +const std::array &DescriptiveComicMetadata::columns() +{ + using M = DescriptiveComicMetadata; + static const std::array columns = { { + { "title", "TEXT", &M::title }, + { "number", "TEXT", &M::number }, + { "isBis", "BOOLEAN", &M::isBis }, + { "count", "INTEGER", &M::count }, + { "volume", "TEXT", &M::volume }, + { "storyArc", "TEXT", &M::storyArc }, + { "arcNumber", "TEXT", &M::arcNumber }, + { "arcCount", "INTEGER", &M::arcCount }, + { "genere", "TEXT", &M::genere }, + { "writer", "TEXT", &M::writer }, + { "penciller", "TEXT", &M::penciller }, + { "inker", "TEXT", &M::inker }, + { "colorist", "TEXT", &M::colorist }, + { "letterer", "TEXT", &M::letterer }, + { "coverArtist", "TEXT", &M::coverArtist }, + { "date", "TEXT", &M::date }, + { "publisher", "TEXT", &M::publisher }, + { "format", "TEXT", &M::format }, + { "color", "BOOLEAN", &M::color }, + { "ageRating", "TEXT", &M::ageRating }, + { "synopsis", "TEXT", &M::synopsis }, + { "characters", "TEXT", &M::characters }, + { "notes", "TEXT", &M::notes }, + { "comicVineID", "TEXT", &M::comicVineID }, + { "editor", "TEXT", &M::editor }, + { "imprint", "TEXT", &M::imprint }, + { "teams", "TEXT", &M::teams }, + { "locations", "TEXT", &M::locations }, + { "series", "TEXT", &M::series }, + { "alternateSeries", "TEXT", &M::alternateSeries }, + { "alternateNumber", "TEXT", &M::alternateNumber }, + { "alternateCount", "INTEGER", &M::alternateCount }, + { "languageISO", "TEXT", &M::languageISO }, + { "seriesGroup", "TEXT", &M::seriesGroup }, + { "mainCharacterOrTeam", "TEXT", &M::mainCharacterOrTeam }, + { "review", "TEXT", &M::review }, + { "tags", "TEXT", &M::tags }, + } }; + return columns; +} + +QStringList DescriptiveComicMetadata::assignments() +{ + QStringList result; + for (const auto &column : columns()) { + const auto name = QString::fromLatin1(column.name); + result.append(name + " = :" + name); + } + return result; +} + +void DescriptiveComicMetadata::bindTo(QSqlQuery &query) const +{ + for (const auto &column : columns()) { + query.bindValue(":" + QString::fromLatin1(column.name), this->*column.member); + } +} + +namespace { + +std::size_t columnOf(QVariant DescriptiveComicMetadata::*field) +{ + const auto &columns = DescriptiveComicMetadata::columns(); + for (std::size_t i = 0; i < columns.size(); ++i) { + if (columns[i].member == field) + return i; + } + Q_UNREACHABLE_RETURN(0); +} + +} + +void DescriptiveMetadataPatch::set(QVariant DescriptiveComicMetadata::*field, const QVariant &value) +{ + values.*field = value; + fields.set(columnOf(field)); +} + +bool DescriptiveMetadataPatch::contains(QVariant DescriptiveComicMetadata::*field) const +{ + return fields.test(columnOf(field)); +} + +bool DescriptiveMetadataPatch::isEmpty() const +{ + return fields.none(); +} + +QStringList DescriptiveMetadataPatch::assignments() const +{ + QStringList result; + const auto &columns = DescriptiveComicMetadata::columns(); + for (std::size_t i = 0; i < columns.size(); ++i) { + if (fields.test(i)) { + const auto name = QString::fromLatin1(columns[i].name); + result.append(name + " = :" + name); + } + } + return result; +} + +void DescriptiveMetadataPatch::bindTo(QSqlQuery &query) const +{ + const auto &columns = DescriptiveComicMetadata::columns(); + for (std::size_t i = 0; i < columns.size(); ++i) { + if (fields.test(i)) + query.bindValue(":" + QString::fromLatin1(columns[i].name), values.*columns[i].member); + } +} + +void DescriptiveMetadataPatch::applyTo(DescriptiveComicMetadata &metadata) const +{ + const auto &columns = DescriptiveComicMetadata::columns(); + for (std::size_t i = 0; i < columns.size(); ++i) { + if (fields.test(i)) + metadata.*columns[i].member = values.*columns[i].member; + } +} + +QStringList DescriptiveComicMetadata::getWriters() const +{ + if (writer.toString().length() > 0) { + return writer.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getPencillers() const +{ + if (penciller.toString().length() > 0) { + return penciller.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getInkers() const +{ + if (inker.toString().length() > 0) { + return inker.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getColorists() const +{ + if (colorist.toString().length() > 0) { + return colorist.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getLetterers() const +{ + if (letterer.toString().length() > 0) { + return letterer.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getCoverArtists() const +{ + if (coverArtist.toString().length() > 0) { + return coverArtist.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getEditors() const +{ + if (editor.toString().length() > 0) { + return editor.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getImprint() const +{ + if (imprint.toString().length() > 0) { + return imprint.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getCharacters() const +{ + if (characters.toString().length() > 0) { + return characters.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getTeams() const +{ + if (teams.toString().length() > 0) { + return teams.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getLocations() const +{ + if (locations.toString().length() > 0) { + return locations.toString().split("\n"); + } + + return QStringList(); +} + +QStringList DescriptiveComicMetadata::getTags() const +{ + if (tags.toString().length() > 0) { + return tags.toString().split("\n"); + } + + return QStringList(); +} + +QString DescriptiveComicMetadata::getStoryArcInfoString() const +{ + if (arcNumber.toString().length() > 0 && arcCount.toString().length() > 0 && storyArc.toString().length() > 0) { + return "(" + arcNumber.toString() + "/" + arcCount.toString() + ") " + storyArc.toString(); + } + + if (arcNumber.toString().length() > 0 && storyArc.toString().length() > 0) { + return "(" + arcNumber.toString() + ") " + storyArc.toString(); + } + + return storyArc.toString().length() > 0 ? storyArc.toString() : ""; +} + +QString DescriptiveComicMetadata::getAlternateSeriesString() const +{ + if (alternateNumber.toString().length() > 0 && alternateCount.toString().length() > 0 && alternateSeries.toString().length() > 0) { + return "(" + alternateNumber.toString() + "/" + alternateCount.toString() + ") " + alternateSeries.toString(); + } + + if (alternateNumber.toString().length() > 0 && alternateSeries.toString().length() > 0) { + return "(" + alternateNumber.toString() + ") " + alternateSeries.toString(); + } + + return alternateSeries.toString().length() > 0 ? alternateSeries.toString() : ""; +} diff --git a/common/descriptive_comic_metadata.h b/common/descriptive_comic_metadata.h new file mode 100644 index 000000000..f021cca37 --- /dev/null +++ b/common/descriptive_comic_metadata.h @@ -0,0 +1,100 @@ +#ifndef DESCRIPTIVE_COMIC_METADATA_H +#define DESCRIPTIVE_COMIC_METADATA_H + +#include +#include + +#include +#include + +class QSqlQuery; +class QSqlRecord; + +struct DescriptiveComicMetadata { + QVariant title; // string + QVariant number; // string + QVariant isBis; // bool + QVariant count; // int + QVariant volume; // string + QVariant storyArc; // string + QVariant arcNumber; // string + QVariant arcCount; // int + QVariant genere; // string + QVariant writer; // string + QVariant penciller; // string + QVariant inker; // string + QVariant colorist; // string + QVariant letterer; // string + QVariant coverArtist; // string + QVariant date; // string, dd/mm/yyyy, parts can be empty + QVariant publisher; // string + QVariant format; // string + QVariant color; // bool + QVariant ageRating; // string + QVariant synopsis; // string + QVariant characters; // string + QVariant notes; // string + QVariant comicVineID; // string + QVariant editor; // string + QVariant imprint; // string + QVariant teams; // string/list + QVariant locations; // string/list + QVariant series; // string + QVariant alternateSeries; // string + QVariant alternateNumber; // string + QVariant alternateCount; // int + QVariant languageISO; // string + QVariant seriesGroup; // string + QVariant mainCharacterOrTeam; // string + QVariant review; // string + QVariant tags; // string/list + + // the people and names of a multi-line field, one per line + QStringList getWriters() const; + QStringList getPencillers() const; + QStringList getInkers() const; + QStringList getColorists() const; + QStringList getLetterers() const; + QStringList getCoverArtists() const; + QStringList getEditors() const; + QStringList getImprint() const; + QStringList getCharacters() const; + QStringList getTeams() const; + QStringList getLocations() const; + QStringList getTags() const; + + QString getStoryArcInfoString() const; + QString getAlternateSeriesString() const; + + struct Column { + const char *name; + const char *sqlType; + QVariant DescriptiveComicMetadata::*member; + }; + + static const std::array &columns(); + + static QStringList assignments(); + + void bindTo(QSqlQuery &query) const; +}; + +// the descriptive fields that an edit sets or clears; a field that is not in the patch keeps its stored value +class DescriptiveMetadataPatch +{ +public: + // a null value clears the field + void set(QVariant DescriptiveComicMetadata::*field, const QVariant &value); + bool contains(QVariant DescriptiveComicMetadata::*field) const; + bool isEmpty() const; + + QStringList assignments() const; + void bindTo(QSqlQuery &query) const; + void applyTo(DescriptiveComicMetadata &metadata) const; + +private: + DescriptiveComicMetadata values; + std::bitset<37> fields; +}; + +#endif diff --git a/common/folder.cpp b/common/folder.cpp index 685f2e85c..8fce68a8f 100644 --- a/common/folder.cpp +++ b/common/folder.cpp @@ -1,81 +1,68 @@ #include "folder.h" -Folder::Folder() - : knownParent(false), - knownId(false), - numChildren(-1) +#include "table_projection.h" + +#include +#include + +const QStringList &Folder::columns() { + static const QStringList columns = { "parentId", "name", "path", "finished", "completed", "numChildren", + "firstChildHash", "customImage", "type", "added", "updated" }; + return columns; } -Folder::Folder(qulonglong folderId, qulonglong parentId, const QString &folderName, const QString &folderPath) - : knownParent(true), - knownId(true), - numChildren(-1) +namespace { + +// `value(k)` is column k of {id} + Folder::columns() +template +Folder readFolder(const Value &value) { - this->id = folderId; - this->parentId = parentId; - this->name = folderName; - this->path = folderPath; + Folder folder; + folder.id = FolderId(value(0).toULongLong()); + folder.parentId = FolderId(value(1).toULongLong()); + folder.name = value(2).toString(); + folder.path = value(3).toString(); + folder.finished = value(4).toBool(); + folder.completed = value(5).toBool(); + folder.numChildren = value(6).toInt(); + folder.firstChildHash = value(7).toString(); + folder.customImage = value(8).toString(); + folder.type = value(9).template value(); + folder.added = value(10).toLongLong(); + folder.updated = value(11).toLongLong(); + return folder; } -Folder::Folder(qulonglong folderId, - qulonglong parentId, - const QString &folderName, - const QString &folderPath, - bool completed, - bool finished, - int numChildren, - const QString &firstChildHash, - const QString &customImage, - YACReader::FileType type, - qint64 added, - qint64 updated) - : knownParent(true), - knownId(true), - numChildren(-1) -{ - this->id = folderId; - this->parentId = parentId; - this->name = folderName; - this->path = folderPath; - this->completed = completed; - this->finished = finished; - this->numChildren = numChildren; - this->firstChildHash = firstChildHash; - this->customImage = customImage; - this->type = type; - this->added = added; - this->updated = updated; } -Folder::Folder(const Folder &folder) +Folder Folder::fromRecord(const QSqlRecord &record, const QString &alias) { - operator=(folder); + const auto indexes = columnIndexes(record, alias); + return readFolder([&](int column) { return record.value(indexes.at(column)); }); } -Folder &Folder::operator=(const Folder &other) +QList Folder::columnIndexes(const QSqlRecord &record, const QString &alias) { - LibraryItem::operator=(other); - - this->knownParent = other.knownParent; - this->knownId = other.knownId; - this->finished = other.finished; - this->completed = other.completed; - this->numChildren = other.numChildren; - this->firstChildHash = other.firstChildHash; - this->customImage = other.customImage; - this->type = other.type; - this->added = other.added; - this->updated = other.updated; + return TableProjection::columnIndexes(record, alias, columns()); +} - return *this; +Folder Folder::fromQuery(const QSqlQuery &query, const QList &columnIndexes) +{ + return readFolder([&](int column) { return query.value(columnIndexes.at(column)); }); } -Folder::Folder(const QString &folderName, const QString &folderPath) - : knownParent(false), - knownId(false), - numChildren(-1) +void Folder::bindTo(QSqlQuery &query) const { - this->name = folderName; - this->path = folderPath; + query.bindValue(":parentId", parentId.value()); + query.bindValue(":name", name); + query.bindValue(":path", path); + query.bindValue(":finished", finished); + query.bindValue(":completed", completed); + query.bindValue(":numChildren", numChildren); + query.bindValue(":firstChildHash", firstChildHash); + query.bindValue(":customImage", customImage); + query.bindValue(":type", static_cast(type)); + query.bindValue(":added", added); + query.bindValue(":updated", updated); } diff --git a/common/folder.h b/common/folder.h index 942ceec43..a48bf3001 100644 --- a/common/folder.h +++ b/common/folder.h @@ -1,61 +1,39 @@ #ifndef __FOLDER_H #define __FOLDER_H -#include "library_item.h" +#include "library_ids.h" #include "yacreader_global.h" -#include +#include +#include -class Folder : public LibraryItem -{ -public: - bool knownParent; - bool knownId; +class QSqlQuery; +class QSqlRecord; - bool finished; // finished means read, the user has read all the content in this folder - bool completed; // completed means the folder has all the content, e.g. a series got its final issue and the user has collected all of them +struct Folder { + FolderId id; + FolderId parentId; + QString name; + QString path; - qint32 numChildren; //-1 for unknown number of children + bool finished = false; // finished means read, the user has read all the content in this folder + bool completed = true; // completed means the folder has all the content, e.g. a series got its final issue and the user has collected all of them + + qint32 numChildren = 0; // the column is nullable, but NULL means 0 QString firstChildHash; // empty for unknown first child QString customImage; // empty for none custom image - YACReader::FileType type; - qint64 added; // epoch in seconds - qint64 updated; // epoch in seconds - - Folder(); - Folder(qulonglong folderId, qulonglong parentId, const QString &folderName, const QString &folderPath); - Folder(qulonglong folderId, - qulonglong parentId, - const QString &folderName, - const QString &folderPath, - bool completed, - bool finished, - int numChildren, - const QString &firstChildHash, - const QString &customImage, - YACReader::FileType type, - qint64 added, - qint64 updated); - Folder(const QString &folderName, const QString &folderPath); - Folder(const Folder &folder); - Folder &operator=(const Folder &other); - - inline void setId(qulonglong sid) - { - id = sid; - knownId = true; - } - inline void setFather(qulonglong pid) - { - parentId = pid; - knownParent = true; - } - - inline bool isDir() const - { - return true; - } + YACReader::FileType type = YACReader::FileType::Comic; + qint64 added = 0; // epoch in seconds + qint64 updated = 0; // epoch in seconds + + friend bool operator==(const Folder &, const Folder &) = default; + + static const QStringList &columns(); + static Folder fromRecord(const QSqlRecord &record, const QString &alias); + static QList columnIndexes(const QSqlRecord &record, const QString &alias); + static Folder fromQuery(const QSqlQuery &query, const QList &columnIndexes); + void bindTo(QSqlQuery &query) const; }; #endif diff --git a/common/library_ids.h b/common/library_ids.h new file mode 100644 index 000000000..21265ff6b --- /dev/null +++ b/common/library_ids.h @@ -0,0 +1,76 @@ +#ifndef LIBRARY_IDS_H +#define LIBRARY_IDS_H + +#include +#include + +#include +#include + +template +class TableId +{ +public: + constexpr TableId() = default; + constexpr explicit TableId(qulonglong value) + : id(value) + { + } + + constexpr qulonglong value() const { return id; } + + friend constexpr bool operator==(TableId, TableId) = default; + friend constexpr auto operator<=>(TableId, TableId) = default; + + friend size_t qHash(TableId key, size_t seed = 0) noexcept { return ::qHash(key.id, seed); } + +private: + qulonglong id = 0; +}; + +using FolderId = TableId; +using ComicId = TableId; +using ComicInfoId = TableId; +using ReadingListId = TableId; +using LabelId = TableId; +using PlaceholderId = TableId; +using SmartListId = TableId; + +enum class ListKind { + ReadingList, + Label, +}; + +// an entry id is unique only in its own table (reading_list_entry or label_entry) +struct ListItemId { + ListKind listKind; + qulonglong id; + + friend constexpr bool operator==(const ListItemId &, const ListItemId &) = default; + friend size_t qHash(const ListItemId &key, size_t seed = 0) noexcept { return qHashMulti(seed, static_cast(key.listKind), key.id); } +}; + +enum class ListFolderKind { + ReadingList, + SmartList, +}; + +// a folder id is unique only in its own table (reading_list_folder or smart_list_folder) +struct ListFolderId { + ListFolderKind kind; + qulonglong id; + + friend constexpr bool operator==(const ListFolderId &, const ListFolderId &) = default; + friend size_t qHash(const ListFolderId &key, size_t seed = 0) noexcept { return qHashMulti(seed, static_cast(key.kind), key.id); } +}; + +// which row this is: a comic in folders and special lists, an entry in reading lists and labels, a placeholder in +// folders (a folder has it once) +using ComicRowId = std::variant; + +inline size_t qHash(const ComicRowId &key, size_t seed = 0) noexcept +{ + return std::visit([&key, seed](const auto &id) { return qHashMulti(seed, key.index(), id); }, key); +} + +#endif diff --git a/common/library_item.cpp b/common/library_item.cpp deleted file mode 100644 index 7e5e877ae..000000000 --- a/common/library_item.cpp +++ /dev/null @@ -1,12 +0,0 @@ - -#include "library_item.h" - -LibraryItem &LibraryItem::operator=(const LibraryItem &other) -{ - this->name = other.name; - this->path = other.path; - this->parentId = other.parentId; - this->id = other.id; - - return *this; -} diff --git a/common/library_item.h b/common/library_item.h deleted file mode 100644 index a9796e05f..000000000 --- a/common/library_item.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __LIBRARY_ITEM_H -#define __LIBRARY_ITEM_H - -#include - -class LibraryItem : public QObject -{ - Q_OBJECT -public: - virtual bool isDir() const = 0; - LibraryItem &operator=(const LibraryItem &other); - QString name; - QString path; - qulonglong parentId; - qulonglong id; -}; - -#endif diff --git a/common/library_item_types.h b/common/library_item_types.h new file mode 100644 index 000000000..6a01d070f --- /dev/null +++ b/common/library_item_types.h @@ -0,0 +1,12 @@ +#ifndef LIBRARY_ITEM_TYPES_H +#define LIBRARY_ITEM_TYPES_H + +#include "comic_db.h" +#include "folder.h" + +#include + +using DiskItem = std::variant; +using FolderContentItem = std::variant; + +#endif diff --git a/common/overloaded.h b/common/overloaded.h new file mode 100644 index 000000000..6b452bd81 --- /dev/null +++ b/common/overloaded.h @@ -0,0 +1,9 @@ +#ifndef OVERLOADED_H +#define OVERLOADED_H + +template +struct overloaded : Ts... { + using Ts::operator()...; +}; + +#endif diff --git a/common/qnaturalsorting.cpp b/common/qnaturalsorting.cpp index 9248a85a8..fe96edc03 100644 --- a/common/qnaturalsorting.cpp +++ b/common/qnaturalsorting.cpp @@ -211,8 +211,3 @@ bool naturalSortLessThanCIFileInfo(const QFileInfo &left, const QFileInfo &right { return naturalSortLessThanCI(left.fileName(), right.fileName()); } - -bool naturalSortLessThanCILibraryItem(LibraryItem *left, LibraryItem *right) -{ - return naturalSortLessThanCI(left->name, right->name); -} diff --git a/common/qnaturalsorting.h b/common/qnaturalsorting.h index e6514215f..c408cfc81 100644 --- a/common/qnaturalsorting.h +++ b/common/qnaturalsorting.h @@ -3,8 +3,6 @@ #ifndef __QNATURALSORTING_H #define __QNATURALSORTING_H -#include "library_item.h" - #include #include #include @@ -13,7 +11,6 @@ int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cas bool naturalSortLessThanCS(const QString &left, const QString &right); bool naturalSortLessThanCI(const QString &left, const QString &right); bool naturalSortLessThanCIFileInfo(const QFileInfo &left, const QFileInfo &right); -bool naturalSortLessThanCILibraryItem(LibraryItem *left, LibraryItem *right); /* Qt only does numeric ordering and case folding in QCollator when it is built with * ICU. A Qt built with -no-icu falls back to the POSIX collator, which drops @@ -39,16 +36,4 @@ int naturalCompareFallback(const QString &s1, const QString &s2, Qt::CaseSensiti bool comicNumberLessThan(const QVariant &leftNumber, const QString &leftName, const QVariant &rightNumber, const QString &rightName); -/* Name-only ordering for server responses that mix folders and comics in a single - * list, where there is no issue number to lean on. Reading order is not this: for - * the comics of a folder use DBHelper::getFolderComicsFromLibraryForReading, which - * sorts by issue number the way the clients do. - **/ -struct LibraryItemSorter { - bool operator()(const LibraryItem *a, const LibraryItem *b) const - { - return naturalSortLessThanCI(a->name, b->name); - } -}; - #endif diff --git a/common/rhi/yacreader_comic_flow_rhi.cpp b/common/rhi/yacreader_comic_flow_rhi.cpp index 780d3fe32..b10610ad2 100644 --- a/common/rhi/yacreader_comic_flow_rhi.cpp +++ b/common/rhi/yacreader_comic_flow_rhi.cpp @@ -12,7 +12,7 @@ YACReaderComicFlow3D::YACReaderComicFlow3D(struct Preset p) worker->flow = this; } -void YACReaderComicFlow3D::setImagePaths(QStringList paths) +void YACReaderComicFlow3D::setImagePaths(QStringList paths, QList grayscale) { worker->reset(); reset(); @@ -25,6 +25,8 @@ void YACReaderComicFlow3D::setImagePaths(QStringList paths) } this->paths = paths; + grayscale.resize(paths.size(), false); + this->grayscale = grayscale; loadingWindowCenter = -1; } @@ -94,7 +96,7 @@ void YACReaderComicFlow3D::updateImageData() if ((i >= 0) && (i < numObjects)) if (!loaded[i] && !failedImageLoads.contains(i)) { if (!paths.isEmpty()) - worker->generate(i, paths.at(i)); + worker->generate(i, paths.at(i), grayscale.at(i)); delete[] indexes; return; } @@ -110,16 +112,18 @@ void YACReaderComicFlow3D::remove(int item) YACReaderFlow3D::remove(item); if (item >= 0 && item < paths.size()) { paths.removeAt(item); + grayscale.removeAt(item); } loadingWindowCenter = -1; worker->unlock(); } -void YACReaderComicFlow3D::add(const QString &path, int index) +void YACReaderComicFlow3D::add(const QString &path, int index, bool grayscale) { worker->lock(); worker->reset(); paths.insert(index, path); + this->grayscale.insert(index, grayscale); YACReaderFlow3D::add(index); loadingWindowCenter = -1; worker->unlock(); @@ -132,6 +136,7 @@ void YACReaderComicFlow3D::resortCovers(QList newOrder) startAnimationTimer(); QList pathsNew; + QList grayscaleNew; QVector loadedNew; QVector marksNew; QVector imagesNew; @@ -143,6 +148,7 @@ void YACReaderComicFlow3D::resortCovers(QList newOrder) } pathsNew << paths.at(i); + grayscaleNew << grayscale.at(i); loadedNew << loaded.at(i); marksNew << marks.at(i); imagesNew << images.at(i); @@ -150,6 +156,7 @@ void YACReaderComicFlow3D::resortCovers(QList newOrder) } paths = pathsNew; + grayscale = grayscaleNew; loaded = loadedNew; marks = marksNew; images = imagesNew; @@ -158,7 +165,7 @@ void YACReaderComicFlow3D::resortCovers(QList newOrder) } // ImageLoader3D implementation -QImage ImageLoader3D::loadImage(const QString &fileName) +QImage ImageLoader3D::loadImage(const QString &fileName, bool grayscale) { QImage image; @@ -180,6 +187,9 @@ QImage ImageLoader3D::loadImage(const QString &fileName) break; } + if (grayscale) + image = image.convertToFormat(QImage::Format_Grayscale8); + return image.convertToFormat(QImage::Format_RGBA8888); } @@ -202,11 +212,12 @@ bool ImageLoader3D::busy() const return working; } -void ImageLoader3D::generate(int index, const QString &fileName) +void ImageLoader3D::generate(int index, const QString &fileName, bool grayscale) { mutex.lock(); this->idx = index; this->fileName = fileName; + this->grayscale = grayscale; this->img = QImage(); this->working = true; const bool shouldStart = !isRunning(); @@ -236,9 +247,10 @@ void ImageLoader3D::run() mutex.lock(); this->working = true; QString fileName = this->fileName; + const bool grayscale = this->grayscale; mutex.unlock(); - QImage image = loadImage(fileName); + QImage image = loadImage(fileName, grayscale); mutex.lock(); this->working = false; diff --git a/common/rhi/yacreader_comic_flow_rhi.h b/common/rhi/yacreader_comic_flow_rhi.h index c64630c10..a1c873da7 100644 --- a/common/rhi/yacreader_comic_flow_rhi.h +++ b/common/rhi/yacreader_comic_flow_rhi.h @@ -13,10 +13,11 @@ class YACReaderComicFlow3D : public YACReaderFlow3D { public: YACReaderComicFlow3D(struct Preset p = defaultYACReaderFlowConfig); - void setImagePaths(QStringList paths); + // grayscale has one entry for each path; a missing entry is false + void setImagePaths(QStringList paths, QList grayscale = { }); void updateImageData() override; void remove(int item) override; - void add(const QString &path, int index); + void add(const QString &path, int index, bool grayscale = false); void resortCovers(QList newOrder); friend class ImageLoader3D; @@ -27,6 +28,7 @@ class YACReaderComicFlow3D : public YACReaderFlow3D protected: QList paths; + QList grayscale; }; class ImageLoader3D : public QThread @@ -35,7 +37,7 @@ class ImageLoader3D : public QThread ImageLoader3D(YACReaderFlow3D *flow); ~ImageLoader3D(); bool busy() const; - void generate(int index, const QString &fileName); + void generate(int index, const QString &fileName, bool grayscale); void reset() { idx = -1; @@ -46,7 +48,7 @@ class ImageLoader3D : public QThread void unlock(); QImage result(); YACReaderFlow3D *flow; - QImage loadImage(const QString &fileName); + QImage loadImage(const QString &fileName, bool grayscale); protected: void run() override; @@ -59,6 +61,7 @@ class ImageLoader3D : public QThread bool working; int idx; QString fileName; + bool grayscale = false; QImage img; }; diff --git a/common/table_projection.cpp b/common/table_projection.cpp new file mode 100644 index 000000000..0e04ddd7b --- /dev/null +++ b/common/table_projection.cpp @@ -0,0 +1,56 @@ +#include "table_projection.h" + +#include + +namespace { + +QString aliased(const QString &alias, const QString &column) +{ + return alias + "_" + column; +} + +} + +QString TableProjection::selectList(const QString &alias, const QStringList &columns) +{ + QStringList result; + result.append(alias + ".id AS " + aliased(alias, "id")); + for (const auto &column : columns) { + result.append(alias + "." + column + " AS " + aliased(alias, column)); + } + return result.join(", "); +} + +QList TableProjection::columnIndexes(const QSqlRecord &record, const QString &alias, const QStringList &columns) +{ + QList indexes; + indexes.reserve(columns.size() + 1); + indexes.append(record.indexOf(aliased(alias, "id"))); + for (const auto &column : columns) { + indexes.append(record.indexOf(aliased(alias, column))); + } + return indexes; +} + +QString TableProjection::insertColumns(const QStringList &columns) +{ + return columns.join(", "); +} + +QString TableProjection::insertValues(const QStringList &columns) +{ + QStringList result; + for (const auto &column : columns) { + result.append(":" + column); + } + return result.join(", "); +} + +QString TableProjection::assignments(const QStringList &columns) +{ + QStringList result; + for (const auto &column : columns) { + result.append(column + " = :" + column); + } + return result.join(", "); +} diff --git a/common/table_projection.h b/common/table_projection.h new file mode 100644 index 000000000..3ed81977c --- /dev/null +++ b/common/table_projection.h @@ -0,0 +1,23 @@ +#ifndef TABLE_PROJECTION_H +#define TABLE_PROJECTION_H + +#include +#include +#include + +class QSqlRecord; + +namespace TableProjection { + +// `columns` are the data columns of a table; every table here has its key in `id`. +QString selectList(const QString &alias, const QStringList &columns); +// positions of `id` and then `columns` in the record; resolve them once per query, not per row +QList columnIndexes(const QSqlRecord &record, const QString &alias, const QStringList &columns); + +QString insertColumns(const QStringList &columns); +QString insertValues(const QStringList &columns); +QString assignments(const QStringList &columns); + +} + +#endif diff --git a/common/yacreader_global.cpp b/common/yacreader_global.cpp index 5d89304ba..4f8366d15 100644 --- a/common/yacreader_global.cpp +++ b/common/yacreader_global.cpp @@ -128,6 +128,7 @@ QDataStream &YACReader::operator<<(QDataStream &stream, const OpenComicSource &s { stream << (quint8)source.source; stream << source.sourceId; + stream << source.entryId; return stream; } @@ -137,6 +138,7 @@ QDataStream &YACReader::operator>>(QDataStream &stream, OpenComicSource &source) stream >> sourceRaw; source.source = (OpenComicSource::Source)sourceRaw; stream >> source.sourceId; + stream >> source.entryId; return stream; } @@ -160,13 +162,12 @@ void YACReader::iterate(const QModelIndex &index, QLibrary *YACReader::load7zLibrary() { #if defined Q_OS_UNIX && !defined Q_OS_MACOS - auto yacreader7zPath = QString(LIBDIR) + "/yacreader/7z.so"; - QFileInfo sevenzlibrary(yacreader7zPath); - if (sevenzlibrary.exists()) { - return new QLibrary(yacreader7zPath); - } else { - return new QLibrary(QString(LIBDIR) + "/7zip/7z.so"); + // the installed library first; a build folder has the bundled one next to the executable + for (const auto &path : { QString(LIBDIR) + "/yacreader/7z.so", QString(LIBDIR) + "/7zip/7z.so" }) { + if (QFileInfo::exists(path)) + return new QLibrary(path); } + return new QLibrary(QCoreApplication::applicationDirPath() + "/utils/7z.so"); #else return new QLibrary(QCoreApplication::applicationDirPath() + "/utils/7z"); #endif diff --git a/common/yacreader_global.h b/common/yacreader_global.h index 1ffd3a1a8..75d71e14d 100644 --- a/common/yacreader_global.h +++ b/common/yacreader_global.h @@ -16,7 +16,7 @@ class QLibrary; // Used to check if the database needs to be updated, the version is stored in the database. // This value is only incremented when the database structure changes. -#define DB_VERSION "9.16.0" +#define DB_VERSION "10.5.0" #define IMPORT_COMIC_INFO_XML_METADATA "IMPORT_COMIC_INFO_XML_METADATA" #define ORGANIZE_FILES_RELATIVE_TO_ROOT "ORGANIZE_FILES_RELATIVE_TO_ROOT" @@ -106,6 +106,8 @@ struct OpenComicSource { Source source; qulonglong sourceId; + // the reading list entry that was opened, 0 if none; a reading list can have a comic more than once + qulonglong entryId = 0; }; QDataStream &operator<<(QDataStream &stream, const OpenComicSource &source); @@ -174,6 +176,16 @@ struct LibraryPaths { return QDir(libraryCustomFoldersCoverPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(folderId)); } + static QString libraryPlaceholdersCoverPathFromLibraryDataPath(const QString &libraryDataPath) // libraryDataPath + /covers/placeholders + { + return QDir(libraryCoversPathFromLibraryDataPath(libraryDataPath)).filePath("placeholders"); + } + + static QString placeholderCoverPathFromDataPath(const QString &libraryDataPath, const QString &coverKey) // libraryDataPath + /covers/placeholders/coverKey + .jpg + { + return QDir(libraryPlaceholdersCoverPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(coverKey)); + } + static QString coverPathFromLibraryDataPath(const QString &libraryDataPath, const QString &hash) // libraryDataPath + /covers/hash + .jpg { return QDir(libraryCoversPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(hash)); @@ -184,11 +196,6 @@ struct LibraryPaths { return id + ".jpg"; } - static QString coverPathWithFileName(const QString &libraryPath, const QString &fileName) // libraryPath + /.yacreaderlibrary/covers/hash + fileName - { - return QDir(libraryCoversFolderPath(libraryPath)).filePath(fileName); - } - static QString idPath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary/id { return QDir(libraryDataPath(libraryPath)).filePath("id"); diff --git a/common/yacreader_global_gui.cpp b/common/yacreader_global_gui.cpp index 80c5e9d6a..9b7fc37a4 100644 --- a/common/yacreader_global_gui.cpp +++ b/common/yacreader_global_gui.cpp @@ -1,5 +1,7 @@ #include "yacreader_global_gui.h" +#include "overloaded.h" + #include #include #include @@ -53,6 +55,65 @@ QList YACReader::mimeDataToComicsIds(const QMimeData *data) return comicIds; } +namespace { + +// the kind of each row in the rows format +enum class RowKind { + Comic, + ReadingListEntry, + LabelEntry, + Placeholder, +}; + +} + +QList YACReader::mimeDataToRows(const QMimeData *data) +{ + QList> values; + QByteArray rawData = data->data(YACReader::YACReaderLibraryComicRowsMimeDataFormat); + QDataStream in(&rawData, QIODevice::ReadOnly); + in >> values; + + QList rows; + rows.reserve(values.size()); + for (const auto &[kind, id] : std::as_const(values)) { + switch (static_cast(kind)) { + case RowKind::Comic: + rows.append(ComicId(id)); + break; + case RowKind::ReadingListEntry: + rows.append(ListItemId { ListKind::ReadingList, id }); + break; + case RowKind::LabelEntry: + rows.append(ListItemId { ListKind::Label, id }); + break; + case RowKind::Placeholder: + rows.append(PlaceholderId(id)); + break; + } + } + return rows; +} + +void YACReader::setRowsMimeData(QMimeData *data, const QList &rows) +{ + QList> values; + values.reserve(rows.size()); + for (const auto &row : rows) { + values.append(std::visit(overloaded { + [](const ComicId &comic) { return QPair(int(RowKind::Comic), comic.value()); }, + [](const ListItemId &entry) { return QPair(int(entry.listKind == ListKind::ReadingList ? RowKind::ReadingListEntry : RowKind::LabelEntry), entry.id); }, + [](const PlaceholderId &placeholder) { return QPair(int(RowKind::Placeholder), placeholder.value()); }, + }, + row)); + } + + QByteArray rawData; + QDataStream out(&rawData, QIODevice::WriteOnly); + out << values; + data->setData(YACReader::YACReaderLibraryComicRowsMimeDataFormat, rawData); +} + QAction *YACReader::wrappedToolbarAction(QAction *action) { auto a = new QAction(action->text()); diff --git a/common/yacreader_global_gui.h b/common/yacreader_global_gui.h index e74262467..a30ab14e2 100644 --- a/common/yacreader_global_gui.h +++ b/common/yacreader_global_gui.h @@ -1,6 +1,7 @@ #ifndef __YACREADER_GLOBAL_GUI_H #define __YACREADER_GLOBAL_GUI_H +#include "library_ids.h" #include "yacreader_global.h" #include @@ -97,7 +98,10 @@ namespace YACReader { static const QString YACReaderLibrarComiscSelectionMimeDataFormat = "application/yacreaderlibrary-comics-ids"; -static const QString YACReaderLibrarSubReadingListMimeDataFormat = "application/yacreaderlibrary-sublist-rows"; +// a row of the lists sidebar: its type (ReadingListModel::TypeList) and its id +static const QString YACReaderLibraryListRowMimeDataFormat = "application/yacreaderlibrary-list-row"; +// the dragged rows of the comics view, in order (ComicRowId) +static const QString YACReaderLibraryComicRowsMimeDataFormat = "application/yacreaderlibrary-comic-rows"; enum FlowType { CoverFlowLike = 0, @@ -124,6 +128,8 @@ QAction *createSeparator(); QIcon noHighlightedIcon(const QString &path); void colorize(QImage &img, const QColor &col); QList mimeDataToComicsIds(const QMimeData *data); +QList mimeDataToRows(const QMimeData *data); +void setRowsMimeData(QMimeData *data, const QList &rows); QAction *wrappedToolbarAction(QAction *action); QPixmap hdpiPixmap(const QString &file, QSize size); QString imageFileLoader(QWidget *parent); diff --git a/compileOSX.sh b/compileOSX.sh index d3486fe2e..26568d97a 100755 --- a/compileOSX.sh +++ b/compileOSX.sh @@ -11,6 +11,11 @@ QT_VERSION=${4:-""} ARCH=${5:-"arm64"} +DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-"12"} + +# The dmg title has a length limit, so the suffix only goes into the file name +DMG_NAME_SUFFIX=${DMG_NAME_SUFFIX:-""} + echo "building macos binaries with these params: ${VERSION} ${BUILD_NUMBER} ${SKIP_CODESIGN} ${QT_VERSION} ${ARCH}" if [ "$6" == "clean" ]; then @@ -37,7 +42,7 @@ cmake -B build \ -DBUILD_NUMBER="${BUILD_NUMBER}" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_OSX_ARCHITECTURES="${ARCHS}" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=12 + -DCMAKE_OSX_DEPLOYMENT_TARGET="${DEPLOYMENT_TARGET}" cmake --build build --parallel @@ -113,11 +118,11 @@ sed -i'' -e "s/#VERSION#/$VERSION/g" dmg.json sed -i'' -e "s/#BUILD_NUMBER#/$BUILD_NUMBER/g" dmg.json sed -i'' -e "s/#QT_VERSION#/$QT_VERSION/g" dmg.json sed -i'' -e "s/#ARCH_NAME#/$ARCH_NAME/g" dmg.json -appdmg dmg.json "$dest.dmg" +appdmg dmg.json "${dest}${DMG_NAME_SUFFIX}.dmg" if [ "$SKIP_CODESIGN" = false ]; then echo "Signing dmg" - codesign --force --deep --sign "Developer ID Application: LUIS ANGEL SAN MARTIN ROD (9B6KKVW3WM)" --options runtime "./${dest}.dmg" + codesign --force --deep --sign "Developer ID Application: LUIS ANGEL SAN MARTIN ROD (9B6KKVW3WM)" --options runtime "./${dest}${DMG_NAME_SUFFIX}.dmg" fi echo "Done!" diff --git a/custom_widgets/CMakeLists.txt b/custom_widgets/CMakeLists.txt index cabc59989..d647c9410 100644 --- a/custom_widgets/CMakeLists.txt +++ b/custom_widgets/CMakeLists.txt @@ -29,16 +29,14 @@ set(WIDGETS_COMMON_SOURCES yacreader_3d_flow_config_widget.cpp ) -# macOS toolbar (shared by both apps) +# --- custom_widgets_reader (YACReader-specific) --- +add_library(custom_widgets_reader STATIC ${WIDGETS_COMMON_SOURCES}) if(APPLE) - list(APPEND WIDGETS_COMMON_SOURCES + target_sources(custom_widgets_reader PRIVATE yacreader_macosx_toolbar.h yacreader_macosx_toolbar.mm ) endif() - -# --- custom_widgets_reader (YACReader-specific) --- -add_library(custom_widgets_reader STATIC ${WIDGETS_COMMON_SOURCES}) target_include_directories(custom_widgets_reader PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) yacreader_apply_build_options(custom_widgets_reader) # App-specific theme.h needed because themable.h → theme_manager.h → theme.h @@ -90,7 +88,7 @@ add_library(custom_widgets_library STATIC ) target_include_directories(custom_widgets_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) yacreader_apply_build_options(custom_widgets_library) -# App-specific theme.h + YACReaderLibrary headers needed by macosx_toolbar +# App-specific theme.h + YACReaderLibrary headers target_include_directories(custom_widgets_library PRIVATE ${PROJECT_SOURCE_DIR}/YACReaderLibrary/themes ${PROJECT_SOURCE_DIR}/YACReaderLibrary diff --git a/custom_widgets/whats_new_dialog.cpp b/custom_widgets/whats_new_dialog.cpp index 9769bb4be..2b2c5f3fb 100644 --- a/custom_widgets/whats_new_dialog.cpp +++ b/custom_widgets/whats_new_dialog.cpp @@ -271,7 +271,9 @@ QString YACReader::WhatsNewDialog::renderHtmlDocument(const QString &content) co QString YACReader::WhatsNewDialog::renderIntro() const { - return "A more capable server!"; + return "Your reading lists just got a lot more useful! " + "Import and share CBL reading lists, organize them in folders, and use smart lists to keep up with your collection. " + "You can also keep track of missing comics, and browse your lists in the WebUI."; } QString YACReader::WhatsNewDialog::renderFooter() const diff --git a/custom_widgets/yacreader_3d_flow_config_widget.cpp b/custom_widgets/yacreader_3d_flow_config_widget.cpp index a1bb4fe1a..5dd990a2b 100644 --- a/custom_widgets/yacreader_3d_flow_config_widget.cpp +++ b/custom_widgets/yacreader_3d_flow_config_widget.cpp @@ -191,7 +191,7 @@ YACReader3DFlowConfigWidget::YACReader3DFlowConfigWidget(QWidget *parent /* = 0 QHBoxLayout *vSyncLayout = new QHBoxLayout; - vSyncCheck = new QCheckBox(tr("Use VSync (improve the image quality in fullscreen mode, worse performance)")); + vSyncCheck = new QCheckBox(tr("Use VSync (improves image quality in fullscreen mode, but reduces performance)")); vSyncLayout->addStretch(); vSyncLayout->addWidget(vSyncCheck); diff --git a/custom_widgets/yacreader_dark_menu.cpp b/custom_widgets/yacreader_dark_menu.cpp deleted file mode 100644 index ce7e1fe6f..000000000 --- a/custom_widgets/yacreader_dark_menu.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "yacreader_dark_menu.h" - -#include -#include -#include - -YACReaderDarkMenu::YACReaderDarkMenu(QWidget *parent) - : QMenu(parent) -{ - // solid color: #454545 - QString style = "QMenu {background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6B6B6B, stop: 1 #424242); " - "border-left: 1px solid qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #BCBCBC, stop: 1 #4C4C4C);" - "border-right: 1px solid qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #BCBCBC, stop: 1 #4C4C4C);" - "border-top: 1px solid #BCBCBC;" - "border-bottom: 1px solid #4C4C4C;" - "padding-top:5px;padding-bottom:5px;}" - "QMenu::separator {height:0px;border-top: 1px solid #292929; border-bottom:1px solid #737373; margin-left:-1px; margin-right:-1px;}" - "QMenu::item {color:#CFD1D1;padding: 5px 25px 5px 32px;}" - "QMenu::item::selected {background-color:#242424;border-top: 1px solid #151515; border-bottom:1px solid #737373;}" - "QMenu::icon {padding-left:15px;}"; - - setStyleSheet(style); - - /* - QPixmap p(":/images/icon.png"); - QLabel * l = new QLabel(); - l->setPixmap(p); - l->move(0,-10); - - //test - YACReaderDarkMenu * customMenu = new YACReaderDarkMenu(this); - customMenu->addAction(toggleFullScreenAction); - customMenu->addAction(createLibraryAction); - customMenu->addSeparator(); - customMenu->addAction(openComicAction); - customMenu->show(); - */ -} \ No newline at end of file diff --git a/custom_widgets/yacreader_dark_menu.h b/custom_widgets/yacreader_dark_menu.h deleted file mode 100644 index ccde1fe27..000000000 --- a/custom_widgets/yacreader_dark_menu.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef YACREADER_DARK_MENU_H -#define YACREADER_DARK_MENU_H - -#include - -class YACReaderDarkMenu : public QMenu -{ - Q_OBJECT -public: - YACReaderDarkMenu(QWidget *parent = 0); -}; - -#endif // YACREADER_DARK_MENU_H \ No newline at end of file diff --git a/custom_widgets/yacreader_field_edit.cpp b/custom_widgets/yacreader_field_edit.cpp index 52e113cff..caf5bed94 100644 --- a/custom_widgets/yacreader_field_edit.cpp +++ b/custom_widgets/yacreader_field_edit.cpp @@ -16,7 +16,6 @@ YACReaderFieldEdit::YACReaderFieldEdit(QWidget *parent) void YACReaderFieldEdit::focusInEvent(QFocusEvent *e) { if (e->reason() == Qt::MouseFocusReason) { - setModified(true); setPlaceholderText(""); } diff --git a/custom_widgets/yacreader_field_plain_text_edit.cpp b/custom_widgets/yacreader_field_plain_text_edit.cpp index ea28a21d5..3e6bf75be 100644 --- a/custom_widgets/yacreader_field_plain_text_edit.cpp +++ b/custom_widgets/yacreader_field_plain_text_edit.cpp @@ -15,7 +15,6 @@ YACReaderFieldPlainTextEdit::YACReaderFieldPlainTextEdit(QWidget *parent) void YACReaderFieldPlainTextEdit::focusInEvent(QFocusEvent *e) { if (e->reason() == Qt::MouseFocusReason || e->reason() == Qt::TabFocusReason) { - document()->setModified(true); if (toPlainText() == tr("Click to overwrite")) setPlainText(""); } diff --git a/custom_widgets/yacreader_library_item_widget.cpp b/custom_widgets/yacreader_library_item_widget.cpp index 1446a28a8..587bdc14c 100644 --- a/custom_widgets/yacreader_library_item_widget.cpp +++ b/custom_widgets/yacreader_library_item_widget.cpp @@ -1,6 +1,7 @@ #include "yacreader_library_item_widget.h" #include "yacreader_global.h" +#include "yacreader_treeview.h" #include #include @@ -13,7 +14,8 @@ YACReaderLibraryItemWidget::YACReaderLibraryItemWidget(QString n /*ame*/, QStrin : QWidget(parent), name(n), path(p), isSelected(false) { QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); + // the options dot ends where the sidebar header icons end + mainLayout->setContentsMargins(0, 0, 4, 0); mainLayout->setSpacing(0); // installEventFilter(this); @@ -25,11 +27,11 @@ YACReaderLibraryItemWidget::YACReaderLibraryItemWidget(QString n /*ame*/, QStrin options = new QToolButton(this); optionsIconLabel = new QLabel(options); - optionsIconLabel->move(4, 3); - optionsIconLabel->setFixedSize(14, 14); + optionsIconLabel->setFixedSize(YACReaderTreeView::rowHeight, YACReaderTreeView::rowHeight); + optionsIconLabel->setAlignment(Qt::AlignCenter); options->setHidden(true); - options->setIconSize(QSize(18, 18)); + options->setFixedSize(YACReaderTreeView::rowHeight, YACReaderTreeView::rowHeight); options->setStyleSheet("QToolButton {border:none;}"); connect(options, &QAbstractButton::clicked, this, &YACReaderLibraryItemWidget::showOptions); /*up = new QToolButton(this); @@ -46,19 +48,19 @@ YACReaderLibraryItemWidget::YACReaderLibraryItemWidget(QString n /*ame*/, QStrin mainLayout->addWidget(icon); mainLayout->addWidget(nameLabel, 1, Qt::AlignLeft); - mainLayout->addWidget(options, Qt::AlignCenter); + mainLayout->addWidget(options, 0, Qt::AlignVCenter); /*mainLayout->addWidget(up); mainLayout->addWidget(down);*/ setLayout(mainLayout); - QString iconStyleSheet = "QLabel {padding:0 0 0 24px; margin:0px}"; + QString iconStyleSheet = "QLabel {padding:0 0 0 22px; margin:0px}"; icon->setStyleSheet(iconStyleSheet); - QString nameLabelStyleSheet = "QLabel {padding:0 0 0 3px; margin:0px;}"; + QString nameLabelStyleSheet = "QLabel {padding:0 0 0 4px; margin:0px;}"; nameLabel->setStyleSheet(nameLabelStyleSheet); - setMinimumHeight(20); + setFixedHeight(YACReaderTreeView::rowHeight); setAttribute(Qt::WA_StyledBackground, true); initTheme(this); diff --git a/custom_widgets/yacreader_macosx_toolbar.h b/custom_widgets/yacreader_macosx_toolbar.h index e2df74c39..b0c896e06 100644 --- a/custom_widgets/yacreader_macosx_toolbar.h +++ b/custom_widgets/yacreader_macosx_toolbar.h @@ -1,60 +1,6 @@ #ifndef YACREADER_MACOSX_TOOLBAR_H #define YACREADER_MACOSX_TOOLBAR_H -#ifdef YACREADER_LIBRARY - -#include "yacreader_main_toolbar.h" -#include "yacreader_search_line_edit.h" - -#include - -class QMenu; - -class YACReaderMacOSXSearchLineEdit : public YACReaderSearchLineEdit -{ -}; - -class YACReaderMacOSXToolbar : public YACReaderMainToolBar -{ - Q_OBJECT -public: - explicit YACReaderMacOSXToolbar(QWidget *parent = 0); - QSize sizeHint() const override; - void addAction(QAction *action); - void addSpace(int size); // size in points - void addStretch(); - YACReaderMacOSXSearchLineEdit *addSearchEdit(); - void updateViewSelectorIcon(const QIcon &icon); - void attachToWindow(QMainWindow *window); - - void *getSearchEditDelegate() { return searchEditDelegate; }; - - void setNativeSearchField(void *field); - void setSearchMenu(QMenu *menu); - void setSearchText(const QString &text, bool notify = true); - void clearSearchText(bool notify = true); - void focusSearch(); - void setSearchEnabled(bool enabled); - QString searchText() const; - void nativeSearchTextChanged(const QString &text); - - QAction *actionFromIdentifier(const QString &identifier); -signals: - void filterChanged(QString); - -private: - void paintEvent(QPaintEvent *) override; - - void *searchEditDelegate; - void *nativeSearchField; - QMenu *searchMenu; - YACReaderMacOSXSearchLineEdit *searchEditProxy; - QString pendingSearchText; - bool searchEnabled; -}; - -#else - #include class YACReaderMacOSXToolbar : public QWidget @@ -66,7 +12,7 @@ class YACReaderMacOSXToolbar : public QWidget void addStretch(); void setMovable(bool movable) { }; - void addSeparator() { }; + void addSeparator(); void setIconSize(const QSize &size) { }; @@ -76,6 +22,4 @@ public slots: void hide(); }; -#endif - #endif // YACREADER_MACOSX_TOOLBAR_H diff --git a/custom_widgets/yacreader_macosx_toolbar.mm b/custom_widgets/yacreader_macosx_toolbar.mm index e5bf036c1..76f18cfc4 100644 --- a/custom_widgets/yacreader_macosx_toolbar.mm +++ b/custom_widgets/yacreader_macosx_toolbar.mm @@ -8,9 +8,11 @@ #import #import +// size is in points, the returned NSImage is backed by a bitmap that matches the screen scale NSImage *QIconToNSImage(const QIcon &icon, const QSize &size, const QColor &color = QColor()) { - QPixmap pixmap = icon.pixmap(size); + const qreal dpr = std::max(qApp->devicePixelRatio(), 2.0); + QPixmap pixmap = icon.pixmap(size, dpr); QImage qImage = pixmap.toImage().convertToFormat(QImage::Format_RGBA8888); if (color.isValid()) { @@ -21,7 +23,7 @@ p.begin(&mask); p.setCompositionMode(QPainter::CompositionMode_SourceIn); QBrush brush(color); - p.fillRect(QRect(0, 0, size.width(), size.height()), brush); + p.fillRect(mask.rect(), brush); p.end(); p.begin(&qImage); @@ -41,7 +43,8 @@ kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGImageRef cgImage = CGBitmapContextCreateImage(context); - NSImage *nsImage = [[NSImage alloc] initWithCGImage:cgImage size:NSMakeSize(qImage.width(), qImage.height())]; + const QSizeF pointSize = QSizeF(qImage.size()) / qImage.devicePixelRatio(); + NSImage *nsImage = [[NSImage alloc] initWithCGImage:cgImage size:NSMakeSize(pointSize.width(), pointSize.height())]; // Clean up CGImageRelease(cgImage); @@ -72,48 +75,41 @@ void bindActionToNSToolbarItem(QAction *action, NSToolbarItem *toolbarItem, cons return; } - __auto_type image = QIconToNSImage(icon, { 24, 24 }, iconColor); + // Same visual size as the SF Symbols used by native toolbars (e.g. Finder) + const QSize iconSize(16, 16); + __auto_type image = QIconToNSImage(icon, iconSize, iconColor); if (image.size.width == 0 || image.size.height == 0) { [image release]; return; } - if (action->isChecked()) { - NSSize size = image.size; - NSImage *decoratedImage = [[NSImage alloc] initWithSize:size]; - - [decoratedImage lockFocus]; - - NSRect rect = NSMakeRect(0, 0, size.width, size.height); - NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:8 yRadius:8]; - [[NSColor colorWithCalibratedRed:0.8 green:0.8 blue:0.8 alpha:0.9] setFill]; - [path fill]; - - NSRect imageRect = NSMakeRect(4, 4, size.width - 8, size.height - 8); - [image drawInRect:imageRect - fromRect:NSZeroRect - operation:NSCompositingOperationSourceOver - fraction:1.0]; - - [decoratedImage unlockFocus]; - - toolbarItem.image = decoratedImage; + if (action->isCheckable()) { + // Checkable items need some room around the icon to draw the checked background + const bool checked = action->isChecked(); + const CGFloat padding = 4; + NSSize iconPointSize = image.size; + NSSize size = NSMakeSize(iconPointSize.width + 2 * padding, iconPointSize.height + 2 * padding); + + toolbarItem.image = [NSImage imageWithSize:size + flipped:NO + drawingHandler:^BOOL(NSRect rect) { + if (checked) { + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:5 yRadius:5]; + [[NSColor colorWithCalibratedRed:0.8 green:0.8 blue:0.8 alpha:0.9] setFill]; + [path fill]; + } + + [image drawInRect:NSMakeRect(padding, padding, iconPointSize.width, iconPointSize.height) + fromRect:NSZeroRect + operation:NSCompositingOperationSourceOver + fraction:1.0]; + return YES; + }]; } else { - NSSize size = image.size; - NSImage *decoratedImage = [[NSImage alloc] initWithSize:size]; - - [decoratedImage lockFocus]; - - NSRect imageRect = NSMakeRect(4, 4, size.width - 8, size.height - 8); - [image drawInRect:imageRect - fromRect:NSZeroRect - operation:NSCompositingOperationSourceOver - fraction:1.0]; - - [decoratedImage unlockFocus]; - - toolbarItem.image = decoratedImage; + // Without an explicit color let AppKit tint the icon (light/dark appearance, disabled state) + [image setTemplate:!iconColor.isValid()]; + toolbarItem.image = image; } [image release]; @@ -144,384 +140,18 @@ void bindActionToNSToolbarItem(QAction *action, NSToolbarItem *toolbarItem, cons update(); } -#ifdef YACREADER_LIBRARY - -@interface YACReaderLibraryToolbarDelegate : NSObject { -@public - YACReaderMacOSXToolbar *mytoolbar; -} - -- (IBAction)itemClicked:(id)sender; - -@end - -@implementation YACReaderLibraryToolbarDelegate - -- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar -{ - Q_UNUSED(toolbar); - - return @[ - @"Back", - @"Forward", - @"Settings", - @"Server", - @"Help", - NSToolbarSpaceItemIdentifier, - @"ToggleView", - NSToolbarSpaceItemIdentifier, - @"Search", - ]; -} - -- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar -{ - Q_UNUSED(toolbar); - - return @[ - @"Back", - @"Forward", - @"Settings", - @"Server", - @"Help", - @"ToggleView", - @"Search", - NSToolbarSpaceItemIdentifier, - ]; -} - -/* -- (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar -{ - Q_UNUSED(toolbar); - - NSMutableArray *array = [[NSMutableArray alloc] init]; - - QList items = mytoolbar->items(); - for (const auto *item : items) { - [array addObject : item->nativeToolBarItem().itemIdentifier]; - } - return array; - //NSMutableArray *array = toolbarPrivate->getItemIdentifiers(toolbarPrivate->items, true); - //[array addObjectsFromArray:toolbarPrivate->getItemIdentifiers(toolbarPrivate->allowedItems, true)]; - //return array; -}*/ - -- (IBAction)itemClicked:(id)sender -{ - NSToolbarItem *item = reinterpret_cast(sender); - QString identifier = QString::fromNSString([item itemIdentifier]); - - QAction *action = mytoolbar->actionFromIdentifier(identifier); - ; - - if (action != nullptr) { - action->trigger(); - } -} - -- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)willBeInserted -{ - Q_UNUSED(toolbar); - Q_UNUSED(willBeInserted); - - QString identifier = QString::fromNSString(itemIdentifier); - - if (identifier == "Search") { - NSSearchToolbarItem *searchItem = [[NSSearchToolbarItem alloc] initWithItemIdentifier:itemIdentifier]; - - searchItem.resignsFirstResponderWithCancel = true; - searchItem.searchField.delegate = id(mytoolbar->getSearchEditDelegate()); - searchItem.toolTip = @"Search"; - mytoolbar->setNativeSearchField(searchItem.searchField); - - return searchItem; - } - - NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier]; - - toolbarItem.target = self; - toolbarItem.action = @selector(itemClicked:); - - QAction *action = mytoolbar->actionFromIdentifier(identifier); - - if (identifier == "Back") { - toolbarItem.navigational = YES; - } else if (identifier == "Forward") { - toolbarItem.navigational = YES; - } - - bindActionToNSToolbarItem(action, toolbarItem); - - return toolbarItem; -} - -- (BOOL)validateToolbarItem:(NSToolbarItem *)item -{ - - QString identifier = QString::fromNSString([item itemIdentifier]); - - if (identifier == "Search") { - return YES; - } - - QAction *action = mytoolbar->actionFromIdentifier(identifier); - - if (action == nullptr) { - return NO; - } - - return action->isEnabled(); -} - -@end - -@interface YACReaderLibrarySearchDelegate : NSObject { -@public - YACReaderMacOSXToolbar *mytoolbar; -} -- (IBAction)searchMenuItemClicked:(id)sender; -@end - -@implementation YACReaderLibrarySearchDelegate - -- (void)searchFieldDidStartSearching:(NSSearchField *)sender -{ -} -- (void)searchFieldDidEndSearching:(NSSearchField *)sender -{ - [sender resignFirstResponder]; -} -- (void)controlTextDidChange:(NSNotification *)notification -{ - NSSearchField *searchField = notification.object; - mytoolbar->nativeSearchTextChanged(QString::fromNSString(searchField.stringValue)); -} -- (IBAction)searchMenuItemClicked:(id)sender -{ - NSMenuItem *item = reinterpret_cast(sender); - NSValue *actionValue = item.representedObject; - auto *action = static_cast(actionValue.pointerValue); - if (action) - action->trigger(); -} - -@end - -YACReaderMacOSXToolbar::YACReaderMacOSXToolbar(QWidget *parent) - : YACReaderMainToolBar(parent), - searchEditDelegate(nullptr), - nativeSearchField(nullptr), - searchMenu(nullptr), - searchEditProxy(nullptr), - searchEnabled(true) -{ - backButton->setIconSize(QSize(24, 24)); - forwardButton->setIconSize(QSize(24, 24)); - settingsButton->setIconSize(QSize(24, 24)); - serverButton->setIconSize(QSize(24, 24)); - helpButton->setIconSize(QSize(24, 24)); - toggleComicsViewButton->setIconSize(QSize(24, 24)); -} - -QSize YACReaderMacOSXToolbar::sizeHint() const -{ - return QSize(400, 36); -} - -void YACReaderMacOSXToolbar::addAction(QAction *action) -{ - if (backButton->defaultAction() == nullptr) { - backButton->setDefaultAction(action); - return; - } - if (forwardButton->defaultAction() == nullptr) { - forwardButton->setDefaultAction(action); - return; - } - if (settingsButton->defaultAction() == nullptr) { - settingsButton->setDefaultAction(action); - return; - } - if (serverButton->defaultAction() == nullptr) { - serverButton->setDefaultAction(action); - return; - } - if (helpButton->defaultAction() == nullptr) { - helpButton->setDefaultAction(action); - return; - } - if (toggleComicsViewButton->defaultAction() == nullptr) { - toggleComicsViewButton->setDefaultAction(action); - return; - } -} - -void YACReaderMacOSXToolbar::addSpace(int size) -{ -} - -void YACReaderMacOSXToolbar::addStretch() -{ -} - -YACReaderMacOSXSearchLineEdit *YACReaderMacOSXToolbar::addSearchEdit() -{ - auto *search = new YACReaderMacOSXSearchLineEdit(); - searchEditProxy = search; - - setSearchWidget(search); - - return search; -} - -void YACReaderMacOSXToolbar::setNativeSearchField(void *field) -{ - nativeSearchField = field; - - auto *nativeField = reinterpret_cast(nativeSearchField); - nativeField.stringValue = pendingSearchText.toNSString(); - nativeField.enabled = searchEnabled; - - setSearchMenu(searchMenu); -} - -void YACReaderMacOSXToolbar::setSearchMenu(QMenu *menu) -{ - searchMenu = menu; - if (!nativeSearchField || !searchMenu || !searchEditDelegate) - return; - - auto *nativeMenu = [[[NSMenu alloc] initWithTitle:@"Search filters"] autorelease]; - for (QAction *action : searchMenu->actions()) { - if (action->isSeparator() && action->text().isEmpty()) { - [nativeMenu addItem:NSMenuItem.separatorItem]; - continue; - } - - auto *item = [[[NSMenuItem alloc] - initWithTitle:action->text().toNSString() - action:action->isSeparator() ? nil : @selector(searchMenuItemClicked:) - keyEquivalent:@""] autorelease]; - item.enabled = !action->isSeparator() && action->isEnabled(); - item.target = action->isSeparator() ? nil : id(searchEditDelegate); - item.representedObject = [NSValue valueWithPointer:action]; - [nativeMenu addItem:item]; - } - - auto *nativeField = reinterpret_cast(nativeSearchField); - auto *cell = reinterpret_cast(nativeField.cell); - cell.searchMenuTemplate = nativeMenu; -} - -void YACReaderMacOSXToolbar::setSearchText(const QString &text, bool notify) -{ - pendingSearchText = text; - - if (nativeSearchField) { - auto *nativeField = reinterpret_cast(nativeSearchField); - nativeField.stringValue = text.toNSString(); - } - - if (searchEditProxy) { - const QSignalBlocker blocker(searchEditProxy); - searchEditProxy->setText(text); - } - - if (notify) - emit filterChanged(text); -} - -void YACReaderMacOSXToolbar::clearSearchText(bool notify) -{ - setSearchText({ }, notify); -} - -void YACReaderMacOSXToolbar::focusSearch() -{ - if (!nativeSearchField) - return; - - auto *nativeField = reinterpret_cast(nativeSearchField); - [nativeField.window makeFirstResponder:nativeField]; -} - -void YACReaderMacOSXToolbar::setSearchEnabled(bool enabled) -{ - searchEnabled = enabled; - if (nativeSearchField) { - auto *nativeField = reinterpret_cast(nativeSearchField); - nativeField.enabled = enabled; - } - if (searchEditProxy) - searchEditProxy->setEnabled(enabled); -} - -QString YACReaderMacOSXToolbar::searchText() const -{ - return pendingSearchText; -} - -void YACReaderMacOSXToolbar::nativeSearchTextChanged(const QString &text) -{ - pendingSearchText = text; - if (searchEditProxy) { - const QSignalBlocker blocker(searchEditProxy); - searchEditProxy->setText(text); - } - emit filterChanged(text); -} - -void YACReaderMacOSXToolbar::updateViewSelectorIcon(const QIcon &icon) -{ -} - -void YACReaderMacOSXToolbar::attachToWindow(QMainWindow *window) -{ - NSView *nsview = (NSView *)window->winId(); - NSWindow *nswindow = [nsview window]; - - YACReaderLibrarySearchDelegate *searchDelegate = [[YACReaderLibrarySearchDelegate alloc] init]; - this->searchEditDelegate = searchDelegate; - searchDelegate->mytoolbar = this; +// Separators split the toolbar items into visual groups, the stretch pushes the following items to the trailing edge +static const char *toolbarStretchProperty = "yacreaderToolbarStretch"; - // Create the NSToolbar - NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"]; - [toolbar setDisplayMode:NSToolbarDisplayModeIconOnly]; - [toolbar setShowsBaselineSeparator:false]; - - __auto_type delegate = [[YACReaderLibraryToolbarDelegate alloc] init]; - delegate->mytoolbar = this; - [toolbar setDelegate:delegate]; - - [nswindow setToolbar:toolbar]; -} - -void YACReaderMacOSXToolbar::paintEvent(QPaintEvent *) -{ -} - -QAction *YACReaderMacOSXToolbar::actionFromIdentifier(const QString &identifier) +static NSToolbarItemIdentifier identifierForAction(QAction *action) { - if (identifier == "Back") { - return backButton->defaultAction(); - } else if (identifier == "Forward") { - return forwardButton->defaultAction(); - } else if (identifier == "Settings") { - return settingsButton->defaultAction(); - } else if (identifier == "Server") { - return serverButton->defaultAction(); - } else if (identifier == "Help") { - return helpButton->defaultAction(); - } else if (identifier == "ToggleView") { - return toggleComicsViewButton->defaultAction(); + if (action->isSeparator()) { + return action->property(toolbarStretchProperty).toBool() ? NSToolbarFlexibleSpaceItemIdentifier : NSToolbarSpaceItemIdentifier; } - return nullptr; + return [NSString stringWithFormat:@"action_%p", action]; } -#else - @interface YACReaderToolbarDelegate : NSObject { @public YACReaderMacOSXToolbar *mytoolbar; @@ -541,7 +171,7 @@ @implementation YACReaderToolbarDelegate NSMutableArray *identifiers = [NSMutableArray arrayWithCapacity:actions.size()]; for (QAction *action : actions) { - [identifiers addObject:[NSString stringWithFormat:@"action_%p", action]]; + [identifiers addObject:identifierForAction(action)]; } return identifiers; @@ -552,12 +182,17 @@ @implementation YACReaderToolbarDelegate Q_UNUSED(toolbar); auto actions = mytoolbar->actions(); - NSMutableArray *identifiers = [NSMutableArray arrayWithCapacity:actions.size()]; + NSMutableArray *identifiers = [NSMutableArray arrayWithCapacity:actions.size() + 2]; for (QAction *action : actions) { - [identifiers addObject:[NSString stringWithFormat:@"action_%p", action]]; + if (!action->isSeparator()) { + [identifiers addObject:identifierForAction(action)]; + } } + [identifiers addObject:NSToolbarSpaceItemIdentifier]; + [identifiers addObject:NSToolbarFlexibleSpaceItemIdentifier]; + return identifiers; } @@ -652,8 +287,19 @@ - (BOOL)validateToolbarItem:(NSToolbarItem *)item [nswindow setToolbar:toolbar]; } +void YACReaderMacOSXToolbar::addSeparator() +{ + auto *separator = new QAction(this); + separator->setSeparator(true); + addAction(separator); +} + void YACReaderMacOSXToolbar::addStretch() { + auto *stretch = new QAction(this); + stretch->setSeparator(true); + stretch->setProperty(toolbarStretchProperty, true); + addAction(stretch); } void YACReaderMacOSXToolbar::setHidden(bool hidden) @@ -673,5 +319,3 @@ - (BOOL)validateToolbarItem:(NSToolbarItem *)item { setHidden(true); } - -#endif diff --git a/custom_widgets/yacreader_social_dialog.cpp b/custom_widgets/yacreader_social_dialog.cpp deleted file mode 100644 index 8c0edf054..000000000 --- a/custom_widgets/yacreader_social_dialog.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "yacreader_social_dialog.h" - -#include "comic_db.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -YACReaderSocialDialog::YACReaderSocialDialog(QWidget *parent) - : QWidget(parent) -{ - - // setWindowFlags(Qt::Window | Qt::Dialog | Qt::FramelessWindowHint); - // setModal(true); - - QToolButton *close = new QToolButton(this); - close->setIcon(QIcon(":/images/social_dialog/close.png")); - - QToolButton *facebook = new QToolButton(this); - facebook->setIcon(QIcon(":/images/social_dialog/facebook.png")); - - QToolButton *twitter = new QToolButton(this); - twitter->setIcon(QIcon(":/images/social_dialog/twitter.png")); - - QToolButton *google = new QToolButton(this); - google->setIcon(QIcon(":/images/social_dialog/google+.png")); - - QString styleSheet = "QToolButton {border:none; }"; - close->setStyleSheet(styleSheet); - facebook->setStyleSheet(styleSheet); - twitter->setStyleSheet(styleSheet); - google->setStyleSheet(styleSheet); - - QLabel *icon = new QLabel(this); - icon->setPixmap(QPixmap(":/images/social_dialog/icon.png")); - - plainText = new QTextEdit(this); - plainText->setStyleSheet("QTextEdit {border:none; padding:11px; font-size:12px; font-weight:bold; color:#525757;}"); - QTextCursor cursor(plainText->textCursor()); - QTextBlockFormat blockFormat = cursor.blockFormat(); - blockFormat.setLineHeight(12, QTextBlockFormat::SingleHeight); - cursor.setBlockFormat(blockFormat); - QLabel *sendTo = new QLabel(tr("send to:"), this); - sendTo->setStyleSheet("QLabel{color:#ABABAB; font-size:12px; font-weight:bold;}"); - - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - resize(sizeHint()); - - close->move(437, 5); - - QWidget *send = new QWidget(this); - QHBoxLayout *sendLayout = new QHBoxLayout; - - QPushButton *follow = new QPushButton(tr("Follow YACReader!"), this); - - follow->setStyleSheet("QPushButton{border:none; color:#FFFFFF;background:#404040; padding: 9px 25px 9px 25px; font-weight:bold; font-size:12px;}" - "QPushButton:hover{background:#E3B800;}"); - - sendLayout->setMargin(0); - sendLayout->setSpacing(0); - - sendLayout->addWidget(sendTo, 1, Qt::AlignHCenter); - sendLayout->addSpacing(11); - sendLayout->addWidget(facebook, 0, Qt::AlignHCenter); - sendLayout->addSpacing(6); - sendLayout->addWidget(twitter, 0, Qt::AlignHCenter); - sendLayout->addSpacing(6); - sendLayout->addWidget(google, 0, Qt::AlignHCenter); - - send->setLayout(sendLayout); - send->move(317, 259); - - icon->move(279, 14); - plainText->setFixedSize(291, 155); - plainText->move(169, 96); - - follow->move(230, 307); - - connect(close, SIGNAL(released()), this, SLOT(close())); -} - -void YACReaderSocialDialog::paintEvent(QPaintEvent *event) -{ - QPainter painter(this); - - // center - painter.fillRect(169, 0, 291, 369, QColor("#F0F0F0")); - painter.fillRect(169, 96, 291, 155, QColor("#FFFFFF")); - - // QPixmap cover = QPixmap("c:/temp/6.jpg").scaledToHeight(369,Qt::SmoothTransformation); - painter.drawPixmap(0, 0, 169, 369, cover, 0, 0, (169 * cover.height()) / 369, cover.height()); - - QPixmap shadow(":/images/social_dialog/shadow.png"); - painter.drawPixmap(169 - shadow.width(), 0, shadow.width(), 369, shadow); - - QPixmap separtor(":/images/social_dialog/separator.png"); - painter.drawPixmap(169, 96 - separtor.height(), separtor); - - QPen pen("#C3CAD6"); - painter.setPen(pen); - painter.drawLine(169, 251, 460, 251); - - QWidget::paintEvent(event); -} - -QSize YACReaderSocialDialog::sizeHint() const -{ - return QSize(460, 369); -} - -void YACReaderSocialDialog::setComic(ComicDB &comic, QString &basePath) -{ - this->cover = comic.info.getCover(basePath).scaledToHeight(369, Qt::SmoothTransformation); - plainText->setText(tr("I am reading %1 using YACReader.").arg(comic.path.split('/').last())); -} \ No newline at end of file diff --git a/custom_widgets/yacreader_social_dialog.h b/custom_widgets/yacreader_social_dialog.h deleted file mode 100644 index a64b3f024..000000000 --- a/custom_widgets/yacreader_social_dialog.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef YACREADER_SOCIAL_DIALOG_H -#define YACREADER_SOCIAL_DIALOG_H - -#include - -class QPixmap; -class QTextEdit; -class ComicDB; - -class YACReaderSocialDialog : public QWidget -{ - Q_OBJECT -public: - explicit YACReaderSocialDialog(QWidget *parent = 0); - QSize sizeHint() const; -signals: - -public slots: - void setComic(ComicDB &comic, QString &basePath); - -protected: - void paintEvent(QPaintEvent *); - -private: - QPixmap cover; - QTextEdit *plainText; -}; - -#endif // YACREADER_SOCIAL_DIALOG_H diff --git a/custom_widgets/yacreader_table_view.cpp b/custom_widgets/yacreader_table_view.cpp index 51a377b6e..94a7fbb9e 100644 --- a/custom_widgets/yacreader_table_view.cpp +++ b/custom_widgets/yacreader_table_view.cpp @@ -1,7 +1,6 @@ #include "yacreader_table_view.h" #include "QsLog.h" -#include "comic_item.h" #include "comic_model.h" #include "cover_utils.h" #include "yacreader_global_gui.h" @@ -38,6 +37,7 @@ YACReaderTableView::YACReaderTableView(QWidget *parent) setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegateForColumn(ComicModel::Rating, new YACReaderRatingDelegate(this)); + setItemDelegateForColumn(ComicModel::State, new YACReaderComicStateDelegate(this)); setEditTriggers(QAbstractItemView::NoEditTriggers); setMouseTracking(true); @@ -220,13 +220,43 @@ void YACReaderTableView::resizeEvent(QResizeEvent *event) QTableView::resizeEvent(event); } +//------------------------------------------------------------------------------ +// YACReaderComicStateDelegate--------------------------------------------------- +//------------------------------------------------------------------------------ +void YACReaderComicStateDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + auto tableView = qobject_cast(parent()); + if (tableView == nullptr) + return; + + const auto kind = index.data(ComicModel::KindRole).toString(); + QIcon icon; + if (kind == QStringLiteral("DeletedComicInList")) + icon = tableView->deletedComicIcon(); + else if (kind == QStringLiteral("PlaceholderInList")) + icon = tableView->placeholderComicIcon(); + else + return; + + const int size = 12; + const QRect iconRect(option.rect.center() - QPoint(size / 2 - 1, size / 2 - 1), QSize(size, size)); + icon.paint(painter, iconRect, Qt::AlignCenter, (option.state & QStyle::State_Selected) ? QIcon::Selected : QIcon::Normal); +} + +QSize YACReaderComicStateDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + return { 20, QStyledItemDelegate::sizeHint(option, index).height() }; +} + //------------------------------------------------------------------------------ // YACReaderRatingDelegate------------------------------------------------------- //------------------------------------------------------------------------------ void YACReaderRatingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - int rating = ((ComicItem *)index.internalPointer())->data(ComicModel::Rating).toInt(); + int rating = index.data(ComicModel::RatingRole).toInt(); StarRating starRating(rating); @@ -254,7 +284,7 @@ QSize YACReaderRatingDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option) - int rating = ((ComicItem *)index.internalPointer())->data(ComicModel::Rating).toInt(); + int rating = index.data(ComicModel::RatingRole).toInt(); StarRating starRating(rating); return starRating.sizeHint(); } @@ -276,7 +306,7 @@ QWidget *YACReaderRatingDelegate::createEditor(QWidget *parent, void YACReaderRatingDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { - int rating = ((ComicItem *)index.internalPointer())->data(ComicModel::Rating).toInt(); + int rating = index.data(ComicModel::RatingRole).toInt(); StarRating starRating(rating); diff --git a/custom_widgets/yacreader_table_view.h b/custom_widgets/yacreader_table_view.h index 29d5ec090..ce2e2fa68 100644 --- a/custom_widgets/yacreader_table_view.h +++ b/custom_widgets/yacreader_table_view.h @@ -15,6 +15,8 @@ class YACReaderTableView : public QTableView, protected Themable explicit YACReaderTableView(QWidget *parent = 0); QColor starRatingColor() const { return theme.comicsViewTable.starRatingColor; } QColor starRatingSelectedColor() const { return theme.comicsViewTable.starRatingSelectedColor; } + QIcon deletedComicIcon() const { return theme.comicsViewTable.deletedComicIcon; } + QIcon placeholderComicIcon() const { return theme.comicsViewTable.placeholderComicIcon; } protected: void applyTheme(const Theme &theme) override; @@ -49,6 +51,20 @@ protected slots: //--- +class YACReaderComicStateDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + YACReaderComicStateDelegate(QWidget *parent = nullptr) + : QStyledItemDelegate(parent) { } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +//--- + class YACReaderRatingDelegate : public QStyledItemDelegate { Q_OBJECT diff --git a/custom_widgets/yacreader_titled_toolbar.cpp b/custom_widgets/yacreader_titled_toolbar.cpp index 0d527dbde..527c9805d 100644 --- a/custom_widgets/yacreader_titled_toolbar.cpp +++ b/custom_widgets/yacreader_titled_toolbar.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +63,8 @@ YACReaderTitledToolBar::YACReaderTitledToolBar(const QString &title, QWidget *pa : QWidget(parent) { QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); + // buttons pad their 16px icon by 4px, so the last icon ends 10px from the edge, like the title starts + mainLayout->setContentsMargins(0, 0, 6, 0); mainLayout->setSpacing(0); QString styleSheet = "QWidget {border:0px;}"; @@ -99,7 +101,11 @@ void YACReaderTitledToolBar::addAction(QAction *action) tb->setCursor(QCursor(Qt::ArrowCursor)); tb->setDefaultAction(action); tb->setIconSize(QSize(16, 16)); - tb->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + tb->setFixedSize(24, 24); + if (action->menu() != nullptr) { + tb->setPopupMode(QToolButton::InstantPopup); + tb->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + } mainLayout->addWidget(tb); } @@ -120,9 +126,9 @@ void YACReaderTitledToolBar::addSepartor() w->setStyleSheet(QString("QWidget {background-color:%1;}").arg(theme.sidebar.separatorColor.name())); separators.append(w); - mainLayout->addSpacing(10); - mainLayout->addWidget(w); - mainLayout->addSpacing(10); + mainLayout->addSpacing(6); + mainLayout->addWidget(w, 0, Qt::AlignVCenter); + mainLayout->addSpacing(6); } void YACReaderTitledToolBar::showBusyIndicator() diff --git a/custom_widgets/yacreader_treeview.cpp b/custom_widgets/yacreader_treeview.cpp index 63c3e33da..426692b4d 100644 --- a/custom_widgets/yacreader_treeview.cpp +++ b/custom_widgets/yacreader_treeview.cpp @@ -12,10 +12,15 @@ YACReaderTreeView::YACReaderTreeView(QWidget *parent) setContextMenuPolicy(Qt::CustomContextMenu); header()->hide(); + setIndentation(20); + setIconSize(QSize(rowIconSize, rowIconSize)); setUniformRowHeights(true); setSelectionBehavior(QAbstractItemView::SelectRows); setAttribute(Qt::WA_MacShowFocusRect, false); + expandTimer.setSingleShot(true); + connect(&expandTimer, &QTimer::timeout, this, &YACReaderTreeView::expandCurrent); + initTheme(this); } @@ -70,8 +75,6 @@ void YACReaderTreeView::dragMoveEvent(QDragMoveEvent *event) QModelIndex underMouse = indexAt(point); if (underMouse.isValid()) { expandPos = point; - connect(&expandTimer, &QTimer::timeout, this, &YACReaderTreeView::expandCurrent); - expandTimer.setSingleShot(true); expandTimer.start(500); } // force mouse hover decoration, TODO why the event loop is not working here? diff --git a/custom_widgets/yacreader_treeview.h b/custom_widgets/yacreader_treeview.h index eb869cbd9..68e160e06 100644 --- a/custom_widgets/yacreader_treeview.h +++ b/custom_widgets/yacreader_treeview.h @@ -18,6 +18,10 @@ class YACReaderTreeView : public QTreeView, protected Themable public: explicit YACReaderTreeView(QWidget *parent = 0); + // shared by every sidebar row (libraries, folders, lists), so it does not change with the platform style + static constexpr int rowHeight = 20; + static constexpr int rowIconSize = 16; + private: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; diff --git a/docs/comic-display-captions.md b/docs/comic-display-captions.md new file mode 100644 index 000000000..bacb7f435 --- /dev/null +++ b/docs/comic-display-captions.md @@ -0,0 +1,35 @@ +# Comic display captions + +Polish follow-up for the 10.5 / API v3 stack. Captions are presentation strings, never replacement metadata. + +## Contexts + +| View | Caption | Number placement | +| --- | --- | --- | +| Desktop grid | Title, otherwise series, volume, then filename / last known filename | Dedicated number field; do not add a prefix to the caption | +| Desktop table Title column | Actual title metadata; blank when absent | Number, series, volume and filename have separate columns | +| Desktop info heading | Number + title, retaining series context when present; otherwise numbered series/volume or filename | Heading must identify the issue on its own | +| Desktop continue-reading card and comic action labels | Numbered metadata caption, otherwise filename | No dedicated number field | +| Exported reading-list HTML | Plain caption | Series / issue / volume are in its separate details line | +| Web cards and reader/details headings | Numbered metadata caption, otherwise filename | No dedicated number field on cards | +| iOS / Android remote cards and table rows | Numbered metadata caption, otherwise filename | No dedicated number field on cards; available and missing comics share the formatter | +| Android local cards and rows | Same formatter, using locally stored metadata | No dedicated number field on cards | +| iOS local grid and table | Stored name by default; readable caption for the readable-title preference or explicit reading order | Both layouts honor the same choice | + +Plain metadata caption priority: title > series > volume > filename. Numbered captions preserve the existing `#N - Title`, `Series #N`, and `#N - Volume` forms. Filename fallbacks remain intact because they commonly already identify the issue. Blank fields are absent; issue `0` and fractional/string numbers are valid. With only a number, standalone captions show `#N`. A completely unknown caption displays `-`. The table Title column is raw metadata, so absent titles stay blank. + +The server sends raw optional title/series/volume/number fields, never `-` or a fabricated "Missing comic" title. API v3 available-comic summaries now include optional raw series and volume, matching missing-comic summaries and full comic details. Frozen v2 summary responses remain unchanged. Mobile PATCH actions reload content; web actions merge returned fields into the current item, retaining v3 caption metadata. + +## Existing data limits + +- iOS local storage has title/volume/number/name but no separate series field. Its formatter uses those available fields; this change does not migrate its database or invent a series from a volume. +- For legacy iOS records without a universal number, integer zero remains the historical unknown-number sentinel. An explicit universal number `"0"` remains a real issue number. +- Missing entries cannot be imported into local mobile libraries; local caption rules apply to imported/available comics. +- This does not implement the deferred A-Z navigation redesign or change explicit reading-list ordering. + +## Validation + +- Desktop: full Release build; all 55 CTest targets pass, including frozen v2 responses and v3 routes. Regression cases cover grid/table versus standalone captions, volume-only metadata, blank fields, issue zero, fractional numbers, filename fallback, and raw API metadata. +- Web: syntax check and nine caption cases executed against the actual formatter; source objects remain unchanged. +- Android: debug app and instrumentation APKs build; all 149 unit tests pass, including shared normal/missing caption cases. Six Parcel instrumentation tests also pass on the existing API 25 emulator. +- iOS: regression cases added for remote normal/missing captions and local legacy/universal numbers. Xcode build and XCTest execution still require macOS; not run on Windows. diff --git a/image_processing/resize_image.cpp b/image_processing/resize_image.cpp index 7a6a94e9c..b469aba22 100644 --- a/image_processing/resize_image.cpp +++ b/image_processing/resize_image.cpp @@ -2,13 +2,20 @@ #include "lancir.h" +#include +#include +#include + static QImage scaleImageLancir(const QImage &image, int width, int height) { - QImage src = (image.format() == QImage::Format_ARGB32) - ? image - : image.convertToFormat(QImage::Format_ARGB32); + // Filter premultiplied channels so invisible pixels cannot bleed colour + // into visible neighbours; convert back to straight alpha afterwards. + // Opaque content is unaffected (premultiply/unpremultiply is the identity + // for alpha 255). + QImage src = image.convertToFormat(QImage::Format_ARGB32) + .convertToFormat(QImage::Format_ARGB32_Premultiplied); - QImage dst(width, height, QImage::Format_ARGB32); + QImage dst(width, height, QImage::Format_ARGB32_Premultiplied); // SrcSSize / NewSSize are in elements; for uint8_t that equals bytes, // so bytesPerLine() covers any Qt row-alignment padding correctly. @@ -20,6 +27,221 @@ static QImage scaleImageLancir(const QImage &image, int width, int height) src.constBits(), src.width(), src.height(), dst.bits(), width, height, 4, ¶ms); + return dst.convertToFormat(QImage::Format_ARGB32); +} + +// ---- Bicubic (Catmull-Rom, separable two-pass resampler) --------------------- + +#include + +struct BicubicTap { + int index; + int weight; // 16.16 fixed point, taps sum to 1<<16 +}; + +// Catmull-Rom kernel (B=0, C=1/2): the classic "bicubic" of image editors. +static inline double bicubicKernel(double t) +{ + t = std::fabs(t); + if (t <= 1.0) + return 1.5 * t * t * t - 2.5 * t * t + 1.0; + if (t < 2.0) + return -0.5 * t * t * t + 2.5 * t * t - 4.0 * t + 2.0; + return 0.0; +} + +// Per-destination-tap weights. When downscaling (scale > 1) the kernel is +// widened by the scale factor so each output pixel integrates the source area +// it covers, which keeps aliasing/moiré under control. +static std::vector> bicubicContributions(int srcSize, int dstSize) +{ + std::vector> contributions(dstSize); + const double scale = double(srcSize) / double(dstSize); + const double filterScale = scale > 1.0 ? scale : 1.0; + + for (int i = 0; i < dstSize; ++i) { + const double center = (i + 0.5) * scale - 0.5; + const int left = std::max(0, int(std::ceil(center - 2.0 * filterScale))); + const int right = std::min(srcSize - 1, int(std::floor(center + 2.0 * filterScale))); + + std::vector raw; + raw.reserve(8); + double sum = 0.0; + for (int j = left; j <= right; ++j) { + const double w = bicubicKernel((j - center) / filterScale); + if (w == 0.0) + continue; + raw.push_back(w); + sum += w; + } + size_t k = 0; + for (int j = left; j <= right && k < raw.size(); ++j) { + const double w = bicubicKernel((j - center) / filterScale); + if (w == 0.0) + continue; + contributions[i].push_back({ j, int(std::llround(raw[k] / sum * 65536.0)) }); + ++k; + } + } + return contributions; +} + +static inline uint8_t clampFinal(int v) +{ + if (v < 0) + return 0; + if (v > 255) + return 255; + return uint8_t(v); +} + +static inline int16_t clampIntermediate(int v) +{ + // signed 8.6 intermediate for the alpha path: must carry Catmull-Rom + // overshoot, NOT clip it — clamping between passes destroys the + // negative-lobe energy that later cancels out and craters quality. + if (v < -(511 << 6)) + return int16_t(-(511 << 6)); + if (v > 511 << 6) + return int16_t(511 << 6); + return int16_t(v); +} + +// ---- gamma-correct (linear-light) resampling tables ---------------------------- +// +// Weighted averaging must happen on linear luminance, not on gamma-encoded sRGB +// bytes; otherwise high-contrast edges darken systematically. Linear values live +// in [0,1] but the dark end collapses (sRGB 1/255 -> linear 0.0003), so they are +// quantized to 14 bits to keep shadow gradation. Alpha is not gamma-encoded and +// stays on the plain 8.6 path. +struct LinearLightLuts { + int16_t decode[256]; // sRGB byte -> linear * 16384 + uint8_t encode[16385]; // linear * 16384 -> sRGB byte + LinearLightLuts() + { + auto toLinear = [](double v) { + return v <= 0.04045 ? v / 12.92 : std::pow((v + 0.055) / 1.055, 2.4); + }; + auto toSrgb = [](double v) { + return v <= 0.0031308 ? v * 12.92 : 1.055 * std::pow(v, 1.0 / 2.4) - 0.055; + }; + for (int c = 0; c < 256; ++c) + decode[c] = int16_t(std::lround(toLinear(c / 255.0) * 16384.0)); + for (int i = 0; i <= 16384; ++i) + encode[i] = uint8_t(std::lround(toSrgb(std::min(1.0, i / 16384.0)) * 255.0)); + } +}; + +static inline int16_t clampLinear14(int v) +{ + // signed 14-bit linear intermediate carrying Catmull-Rom overshoot + if (v < -8192) + return int16_t(-8192); + if (v > 24576) + return int16_t(24576); + return int16_t(v); +} + +static QImage scaleImageBicubic(const QImage &image, int width, int height) +{ + QImage src = (image.format() == QImage::Format_ARGB32) + ? image + : image.convertToFormat(QImage::Format_ARGB32); + + static const LinearLightLuts luts; // function-local static: thread-safe init + + const int sw = src.width(); + const int sh = src.height(); + + // Horizontal pass. RGB is PREMULTIPLIED by alpha (in linear light) and + // stored as signed 14-bit linear; alpha itself is filtered straight in 8.6. + // Filtering premultiplied channels is what keeps invisible pixels from + // bleeding colour into visible neighbours (opaque red next to fully + // transparent blue must shrink to red at 50% alpha, not purple). + std::vector tmp(size_t(width) * sh * 4); + const auto hWeights = bicubicContributions(sw, width); + const auto hPass = [&](int yBegin, int yEnd) { + for (int y = yBegin; y < yEnd; ++y) { + const QRgb *row = reinterpret_cast(src.constScanLine(y)); + int16_t *outRow = &tmp[size_t(y) * width * 4]; + for (int x = 0; x < width; ++x) { + int32_t b = 0, g = 0, r = 0, a = 0; + for (const auto &tap : hWeights[x]) { + const QRgb px = row[tap.index]; + const int pa = qAlpha(px); + // Opaque stays exact (no rounding); others scale linear RGB by pa. + const int pm = (pa == 255) ? 256 : pa; + b += int32_t(tap.weight * ((int32_t(luts.decode[qBlue(px)]) * pm) >> 8)); + g += int32_t(tap.weight * ((int32_t(luts.decode[qGreen(px)]) * pm) >> 8)); + r += int32_t(tap.weight * ((int32_t(luts.decode[qRed(px)]) * pm) >> 8)); + a += int32_t(tap.weight * pa); + } + int16_t *out = &outRow[size_t(x) * 4]; + out[0] = clampLinear14((b + (1 << 15)) >> 16); + out[1] = clampLinear14((g + (1 << 15)) >> 16); + out[2] = clampLinear14((r + (1 << 15)) >> 16); + out[3] = clampIntermediate((a + (1 << 15)) >> 16 << 6); + } + } + }; + + // Vertical pass into the destination image, un-premultiplying with the + // filtered alpha before encoding back to sRGB. + QImage dst(width, height, QImage::Format_ARGB32); + const auto vWeights = bicubicContributions(sh, height); + const auto vPass = [&](int yBegin, int yEnd) { + for (int y = yBegin; y < yEnd; ++y) { + QRgb *row = reinterpret_cast(dst.scanLine(y)); + for (int x = 0; x < width; ++x) { + int64_t b = 0, g = 0, r = 0, a = 0; + for (const auto &tap : vWeights[y]) { + const int16_t *in = &tmp[(size_t(tap.index) * width + x) * 4]; + b += int64_t(tap.weight) * in[0]; + g += int64_t(tap.weight) * in[1]; + r += int64_t(tap.weight) * in[2]; + a += int64_t(tap.weight) * in[3]; + } + const uint8_t aOut = clampFinal(int((a + (1 << 21)) >> 22)); + auto enc = [aOut](int64_t acc) -> uint8_t { + // acc = sum(2^16 weights * 2^14 premultiplied linear) -> prem14 + int p = int((acc + (int64_t(1) << 15)) >> 16); + if (p <= 0) + return 0; + if (p > 16384) + p = 16384; + int i = (aOut == 0 || aOut == 255) + ? p + : int(std::min(16384, (int64_t(p) * 255 + aOut / 2) / aOut)); + return luts.encode[i]; + }; + row[x] = qRgba(enc(r), enc(g), enc(b), aOut); + } + } + }; + + // Modest parallelism: scaling already runs off the GUI thread (dedicated + // scale pool in continuous mode), so this just splits the heavy passes + // across cores without touching any Qt thread pool. + const unsigned hw = std::max(1u, std::min(4u, std::thread::hardware_concurrency())); + if (hw == 1 || (size_t(width) * sh) < 262144) { // < 512x512 source: threading overhead not worth it + hPass(0, sh); + vPass(0, height); + } else { + std::vector threads; + threads.reserve(hw - 1); + for (unsigned t = 1; t < hw; ++t) + threads.emplace_back(hPass, int(uint64_t(sh) * t / hw), int(uint64_t(sh) * (t + 1) / hw)); + hPass(0, int(uint64_t(sh) / hw)); + for (auto &t : threads) + t.join(); + + threads.clear(); + for (unsigned t = 1; t < hw; ++t) + threads.emplace_back(vPass, int(uint64_t(height) * t / hw), int(uint64_t(height) * (t + 1) / hw)); + vPass(0, int(uint64_t(height) / hw)); + for (auto &t : threads) + t.join(); + } return dst; } @@ -37,6 +259,8 @@ QPixmap scalePixmap(const QPixmap &pixmap, int width, int height, ScaleMethod me return pixmap.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); case ScaleMethod::Lanczos: return QPixmap::fromImage(scaleImageLancir(pixmap.toImage(), width, height)); + case ScaleMethod::Bicubic: + return QPixmap::fromImage(scaleImageBicubic(pixmap.toImage(), width, height)); } return pixmap; } @@ -55,6 +279,8 @@ QImage scaleImage(const QImage &image, int width, int height, ScaleMethod method return image.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); case ScaleMethod::Lanczos: return scaleImageLancir(image, width, height); + case ScaleMethod::Bicubic: + return scaleImageBicubic(image, width, height); } return image; } diff --git a/image_processing/resize_image.h b/image_processing/resize_image.h index ae3390fe1..b9bca040c 100644 --- a/image_processing/resize_image.h +++ b/image_processing/resize_image.h @@ -7,7 +7,8 @@ enum class ScaleMethod { Nearest = 0, Bilinear = 1, - Lanczos = 2 + Lanczos = 2, + Bicubic = 3 }; // Base scaling API — callers are responsible for supplying the correct target dimensions. diff --git a/images/clearSearchNew.svg b/images/clearSearchNew.svg index a5d0bb46d..ba271f8e1 100644 --- a/images/clearSearchNew.svg +++ b/images/clearSearchNew.svg @@ -1 +1 @@ - + diff --git a/images/comics_view_table/deletedComic.svg b/images/comics_view_table/deletedComic.svg new file mode 100644 index 000000000..de347a8b6 --- /dev/null +++ b/images/comics_view_table/deletedComic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/comics_view_table/placeholderComic.svg b/images/comics_view_table/placeholderComic.svg new file mode 100644 index 000000000..2e0b339fc --- /dev/null +++ b/images/comics_view_table/placeholderComic.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/images/comics_view_toolbar/big_size_grid_zoom.svg b/images/comics_view_toolbar/big_size_grid_zoom.svg index 0717f0535..beecec6d2 100644 --- a/images/comics_view_toolbar/big_size_grid_zoom.svg +++ b/images/comics_view_toolbar/big_size_grid_zoom.svg @@ -1,15 +1,24 @@ - - + - - - - - \ No newline at end of file + + + + + + diff --git a/images/comics_view_toolbar/getInfo.svg b/images/comics_view_toolbar/getInfo.svg index a9884473c..1c93098fe 100644 --- a/images/comics_view_toolbar/getInfo.svg +++ b/images/comics_view_toolbar/getInfo.svg @@ -1,13 +1,15 @@ - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/images/comics_view_toolbar/showRecentIndicator.svg b/images/comics_view_toolbar/showRecentIndicator.svg index 0d82d6c99..19544b9e8 100644 --- a/images/comics_view_toolbar/showRecentIndicator.svg +++ b/images/comics_view_toolbar/showRecentIndicator.svg @@ -8,5 +8,5 @@ } - + \ No newline at end of file diff --git a/images/comics_view_toolbar/show_comic_info.svg b/images/comics_view_toolbar/show_comic_info.svg index 22b596bd6..19da630ca 100644 --- a/images/comics_view_toolbar/show_comic_info.svg +++ b/images/comics_view_toolbar/show_comic_info.svg @@ -1,15 +1,23 @@ - - + - - - - - \ No newline at end of file + + + + + diff --git a/images/comics_view_toolbar/small_size_grid_zoom.svg b/images/comics_view_toolbar/small_size_grid_zoom.svg index eb4d48a7f..58d7bfc69 100644 --- a/images/comics_view_toolbar/small_size_grid_zoom.svg +++ b/images/comics_view_toolbar/small_size_grid_zoom.svg @@ -1,17 +1,29 @@ - - + - - - - - - - \ No newline at end of file + + + + + + + + + + + diff --git a/images/empty_container/smart_list_card.svg b/images/empty_container/smart_list_card.svg new file mode 100644 index 000000000..e7e98e13a --- /dev/null +++ b/images/empty_container/smart_list_card.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/images/iconSearchNew.svg b/images/iconSearchNew.svg index 797143736..3989d5516 100644 --- a/images/iconSearchNew.svg +++ b/images/iconSearchNew.svg @@ -1 +1 @@ - + diff --git a/images/lists/default_2.svg b/images/lists/default_2.svg index eea9ed7e5..f59ac5361 100644 --- a/images/lists/default_2.svg +++ b/images/lists/default_2.svg @@ -23,6 +23,6 @@ - - + + \ No newline at end of file diff --git a/images/lists/smart_list.svg b/images/lists/smart_list.svg new file mode 100644 index 000000000..be8f8e272 --- /dev/null +++ b/images/lists/smart_list.svg @@ -0,0 +1,2 @@ + + diff --git a/images/main_toolbar/back.svg b/images/main_toolbar/back.svg index f6a4cc94d..84aa6ca36 100644 --- a/images/main_toolbar/back.svg +++ b/images/main_toolbar/back.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/flow.svg b/images/main_toolbar/flow.svg index c84719fc6..79f8eaace 100644 --- a/images/main_toolbar/flow.svg +++ b/images/main_toolbar/flow.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/forward.svg b/images/main_toolbar/forward.svg index 2a786f996..7c5184b5b 100644 --- a/images/main_toolbar/forward.svg +++ b/images/main_toolbar/forward.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/fullscreen.svg b/images/main_toolbar/fullscreen.svg index b7ac674c7..0990bb74c 100644 --- a/images/main_toolbar/fullscreen.svg +++ b/images/main_toolbar/fullscreen.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/grid.svg b/images/main_toolbar/grid.svg index 4787059c1..d760e8183 100644 --- a/images/main_toolbar/grid.svg +++ b/images/main_toolbar/grid.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/help.svg b/images/main_toolbar/help.svg index ce5fc309e..61c057902 100644 --- a/images/main_toolbar/help.svg +++ b/images/main_toolbar/help.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/info.svg b/images/main_toolbar/info.svg index a8e8bb548..8b100e702 100644 --- a/images/main_toolbar/info.svg +++ b/images/main_toolbar/info.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/server.svg b/images/main_toolbar/server.svg index 57c4f2d76..0257e83f2 100644 --- a/images/main_toolbar/server.svg +++ b/images/main_toolbar/server.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/main_toolbar/settings.svg b/images/main_toolbar/settings.svg index d97d6f54e..1b966b776 100644 --- a/images/main_toolbar/settings.svg +++ b/images/main_toolbar/settings.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/images/sidebar/colapse.svg b/images/sidebar/colapse.svg index fcb89d1cc..4f70c5c4b 100644 --- a/images/sidebar/colapse.svg +++ b/images/sidebar/colapse.svg @@ -12,6 +12,6 @@ } - - + + \ No newline at end of file diff --git a/images/sidebar/expand.svg b/images/sidebar/expand.svg index 22ee714fe..72bbd75a8 100644 --- a/images/sidebar/expand.svg +++ b/images/sidebar/expand.svg @@ -12,6 +12,6 @@ } - - + + \ No newline at end of file diff --git a/images/unavailableCover.png b/images/unavailableCover.png new file mode 100644 index 000000000..c3d8e5fc6 Binary files /dev/null and b/images/unavailableCover.png differ diff --git a/images/unavailableCover.svg b/images/unavailableCover.svg new file mode 100644 index 000000000..a2177168e --- /dev/null +++ b/images/unavailableCover.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/release/server/docroot/css/webui.css b/release/server/docroot/css/webui.css index 6e60398fb..ac2344fba 100644 --- a/release/server/docroot/css/webui.css +++ b/release/server/docroot/css/webui.css @@ -472,6 +472,86 @@ input[type="time"]:focus-visible { margin: 22px auto 0; } +.connect-dialog { + width: min(640px, 100%); +} + +.connect-body { + display: grid; + grid-template-columns: 220px minmax(0, 1fr); + align-items: start; + gap: 26px; + margin-top: 24px; +} + +/* Always dark-on-white so phone cameras read it in both themes. */ +.connect-qr { + overflow: hidden; + border: 1px solid var(--border); + border-radius: 14px; + background: #fff; + box-shadow: var(--shadow); + line-height: 0; +} + +.connect-qr-code[hidden] { + display: none; +} + +.connect-qr-image { + display: block; + width: 100%; + height: auto; +} + +.connect-details { + display: flex; + min-width: 0; + flex-direction: column; + gap: 7px; +} + +.connect-address { + font-size: 21px; +} + +.connect-picker { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 8px; +} + +.connect-picker[hidden] { + display: none; +} + +.connect-picker-hint { + color: var(--text-muted); + font-size: 11.5px; + line-height: 1.45; +} + +.connect-picker select { + width: 100%; +} + +.connect-apps { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 18px; + padding-top: 18px; + border-top: 1px solid var(--border); +} + +.connect-store-links { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; +} + .sidebar-donation { display: flex; flex-direction: column; @@ -748,72 +828,8 @@ input[type="time"]:focus-visible { padding: 32px; } -.status-card { - position: relative; - display: flex; - align-items: center; - gap: 28px; - overflow: hidden; - padding: 34px 36px; - border: 1px solid var(--border); - border-radius: 18px; - background: radial-gradient(130% 150% at 100% 0%, var(--accent-soft), transparent 56%), var(--surface); - box-shadow: var(--shadow); -} - -.status-copy { - display: flex; - min-width: 0; - flex: 1; - flex-direction: column; - align-items: flex-start; - gap: 14px; -} - -.status-pill { - display: inline-flex; - align-items: center; - gap: 9px; - padding: 5px 12px 5px 10px; - border: 1px solid var(--online); - border-radius: 999px; - background: var(--online-soft); - color: var(--online); - font-size: 12px; - font-weight: 700; - letter-spacing: 0.03em; - text-transform: uppercase; -} - -.status-pill .online-dot { - width: 8px; - height: 8px; -} - -.status-heading { - max-width: 720px; - margin: 0; - font-size: clamp(28px, 4vw, 34px); - font-weight: 750; - letter-spacing: -0.03em; - line-height: 1.08; - text-wrap: balance; -} - -.status-description { - max-width: 52ch; - margin: 0; - color: var(--text-dim); - font-size: 15px; - line-height: 1.55; -} - -.hero-logo { - width: 84px; - height: 84px; - flex: none; - border-radius: 20px; - box-shadow: 0 8px 24px rgba(236, 139, 12, 0.35); +.topbar-action { + margin-left: auto; } .section { @@ -823,6 +839,10 @@ input[type="time"]:focus-visible { margin-top: 28px; } +.content > .section:first-child { + margin-top: 0; +} + .section-header { display: flex; align-items: center; @@ -980,6 +1000,74 @@ input[type="time"]:focus-visible { background: transparent; } +.library-actions { + position: relative; + z-index: 2; + display: flex; + flex: none; + gap: 2px; +} + +.library-notice-toggle.icon-button, +.library-notice-toggle.icon-button:hover, +.library-notice-toggle.icon-button[aria-expanded="true"] { + color: var(--warning); +} + +.library-notice-toggle.icon-button:hover, +.library-notice-toggle.icon-button[aria-expanded="true"] { + background: var(--warning-soft); +} + +.library-notice-popover { + display: flex; + width: min(300px, calc(100vw - 48px)); + flex-direction: column; + gap: 10px; + padding: 14px; + cursor: default; +} + +.library-notice-popover[hidden] { + display: none; +} + +.library-notice-popover h3 { + margin: 0; + font-size: 14px; + font-weight: 700; +} + +.library-notice-popover p { + margin: 0; + color: var(--text-dim); + font-size: 13px; + line-height: 1.5; +} + +.library-notice-popover .notice-warning { + padding: 9px 10px; + border-radius: 8px; + background: var(--warning-soft); + color: var(--text); +} + +.library-notice-popover .notice-owner { + color: var(--text-muted); + font-family: "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + font-size: 11.5px; + overflow-wrap: anywhere; +} + +.library-notice-popover .notice-error { + color: var(--danger); +} + +.library-notice-popover .primary-button { + align-self: flex-end; + padding: 8px 14px; +} + .library-menu-popover { position: absolute; top: calc(100% + 6px); @@ -1485,6 +1573,106 @@ input[type="time"]:focus-visible { object-fit: cover; } +/* deleted comics and placeholders, as in the desktop */ +.missing-comic-cover .browser-cover-image { + filter: grayscale(1); +} + +/* a list without a cover: the art of its type on the card, as in the desktop */ +.list-art { + position: absolute; + inset: 0; + border: 1px solid var(--border); + border-radius: 10px; + background: linear-gradient(145deg, var(--surface-subtle), var(--surface)); +} + +.list-art::before { + position: absolute; + inset: 0; + background-color: var(--text-muted); + content: ""; + -webkit-mask: var(--list-art) center / 60% auto no-repeat; + mask: var(--list-art) center / 60% auto no-repeat; +} + +.reading-list-art { + --list-art: url("/images/webui/empty_reading_list.svg"); +} + +.smart-list-art { + --list-art: url("/images/webui/smart_list_card.svg"); +} + +.label-art { + --list-art: url("/images/webui/empty_label.svg"); +} + +.label-art::before { + background-color: var(--label-color, var(--text-muted)); +} + +/* the label colors of the desktop themes */ +.label-color-1 { + --label-color: #F67A7B; +} + +.label-color-2 { + --label-color: #F5C240; +} + +.label-color-3 { + --label-color: #F2E446; +} + +.label-color-4 { + --label-color: #ADE738; +} + +.label-color-5 { + --label-color: #A0FDDB; +} + +.label-color-6 { + --label-color: #82C7FF; +} + +.label-color-7 { + --label-color: #8F95FF; +} + +.label-color-8 { + --label-color: #D692FC; +} + +.label-color-9 { + --label-color: #FD9CDA; +} + +.label-color-10 { + --label-color: #FCFCFC; +} + +.label-color-11 { + --label-color: #CBCBCB; +} + +.label-color-12 { + --label-color: #B7B7B7; +} + +:root[data-theme="dark"] .label-color-11 { + --label-color: #AFAFAF; +} + +:root[data-theme="dark"] .label-color-12 { + --label-color: #7C7C7C; +} + +.list-cover .browser-cover-image { + border-radius: 10px; +} + .cover-placeholder { display: grid; overflow: hidden; @@ -1726,6 +1914,20 @@ input[type="time"]:focus-visible { content: ""; } +/* the art of the list in its color, as on its card */ +.list-state-icon { + background: var(--surface-subtle); + background: color-mix(in srgb, var(--label-color, var(--text-muted)) 16%, transparent); +} + +.list-state-icon::before { + width: 28px; + height: 28px; + content: ""; + -webkit-mask: var(--list-art) center / contain no-repeat; + mask: var(--list-art) center / contain no-repeat; +} + .browser-state h2 { margin: 0 0 8px; color: var(--text); @@ -1791,6 +1993,11 @@ input[type="time"]:focus-visible { font-size: 11.5px; } +.missing-comic-note { + margin: 10px 0 0; + color: var(--text-muted); +} + .comic-facts { display: flex; flex-wrap: wrap; @@ -2213,6 +2420,60 @@ input[type="time"]:focus-visible { cursor: default; } +.reader-comic-overlay { + display: flex; + position: absolute; + z-index: 4; + inset: 0; + padding: 20px; + align-items: center; + justify-content: flex-end; + background: rgba(0, 0, 0, 0.7); +} + +.reader-comic-overlay.previous { + justify-content: flex-start; +} + +.reader-comic-overlay-cover { + padding: 0; + overflow: hidden; + border: 0; + border-radius: 11px; + background: transparent; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.7); + cursor: pointer; +} + +.reader-comic-overlay-cover img { + display: block; + width: auto; + height: auto; + max-width: min(45vw, 300px); + max-height: 70vh; +} + +.reader-comic-overlay-close { + position: absolute; + bottom: 24px; + left: 50%; + width: 125px; + height: 48px; + border: 0; + border-radius: 24px; + background: rgba(0, 0, 0, 0.25); + color: #fff; + cursor: pointer; + font: inherit; + transform: translateX(-50%); +} + +.reader-comic-overlay-cover:focus-visible, +.reader-comic-overlay-close:focus-visible { + outline: 2px solid #fff; + outline-offset: 2px; +} + .reader-page-button-icon { width: 26px; height: 26px; @@ -2320,6 +2581,10 @@ input[type="time"]:focus-visible { overflow: hidden; } +.browser-settings-card { + margin-top: 18px; +} + .settings-card-heading { display: flex; align-items: center; @@ -2814,15 +3079,13 @@ input[type="time"]:disabled { height: 22px; } - .status-card { - align-items: flex-start; - padding: 26px 24px; + .connect-body { + grid-template-columns: 1fr; } - .hero-logo { - width: 58px; - height: 58px; - border-radius: 15px; + .connect-qr { + width: min(240px, 100%); + margin: 0 auto; } .system-grid, diff --git a/release/server/docroot/images/webui/empty_label.svg b/release/server/docroot/images/webui/empty_label.svg new file mode 100644 index 000000000..3d5d6d00a --- /dev/null +++ b/release/server/docroot/images/webui/empty_label.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/release/server/docroot/images/webui/empty_reading_list.svg b/release/server/docroot/images/webui/empty_reading_list.svg new file mode 100644 index 000000000..9419898c4 --- /dev/null +++ b/release/server/docroot/images/webui/empty_reading_list.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/release/server/docroot/images/webui/smart_list_card.svg b/release/server/docroot/images/webui/smart_list_card.svg new file mode 100644 index 000000000..e7e98e13a --- /dev/null +++ b/release/server/docroot/images/webui/smart_list_card.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/release/server/docroot/images/webui/unavailableCover.svg b/release/server/docroot/images/webui/unavailableCover.svg new file mode 100644 index 000000000..a2177168e --- /dev/null +++ b/release/server/docroot/images/webui/unavailableCover.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/release/server/docroot/js/webui.js b/release/server/docroot/js/webui.js index 3542aefa6..91872e214 100644 --- a/release/server/docroot/js/webui.js +++ b/release/server/docroot/js/webui.js @@ -15,6 +15,40 @@ return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } + var recentDaysKey = "yacreader-webui-recent-days"; + + // the days of Recent, 1 to 30 as in the apps + function recentDays() { + try { + var days = Number(localStorage.getItem(recentDaysKey)); + if (Number.isInteger(days) && days >= 1 && days <= 30) { + return days; + } + } catch (error) { + } + return 7; + } + + function initRecentDaysSetting() { + var select = document.querySelector("[data-recent-days]"); + if (!select) { + return; + } + for (var days = 1; days <= 30; days++) { + var option = document.createElement("option"); + option.value = String(days); + option.textContent = days === 1 ? "1 day" : days + " days"; + select.appendChild(option); + } + select.value = String(recentDays()); + select.addEventListener("change", function () { + try { + localStorage.setItem(recentDaysKey, select.value); + } catch (error) { + } + }); + } + function applyTheme(theme) { root.dataset.theme = theme; @@ -232,6 +266,14 @@ closeButton.focus(); } + function dropSavedSettingsParameter() { + var url = new URL(window.location.href); + if (url.searchParams.has("saved")) { + url.searchParams.delete("saved"); + history.replaceState(history.state, "", url.pathname + url.search + url.hash); + } + } + function initSidebarActions() { var sidebar = document.querySelector(".sidebar"); var serverSummary = sidebar && sidebar.querySelector(".server-summary"); @@ -412,6 +454,7 @@ var progressSyncPromise = Promise.resolve(); var historyNavigationPending = false; var historyTraversalPending = false; + var openedFromCard = false; var browserItemCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" @@ -436,7 +479,8 @@ if (left.view === "search") { return left.query === right.query; } - return String(left.itemId || "") === String(right.itemId || ""); + return String(left.itemId || "") === String(right.itemId || "") + && String(left.missingKey || "") === String(right.missingKey || ""); } function browserViewportTop() { @@ -523,6 +567,7 @@ if (pushHistory) { saveCurrentScrollPosition(returnToCard); } + openedFromCard = Boolean(pushHistory && returnToCard); historyNavigationPending = true; } @@ -540,6 +585,7 @@ : Object.assign({}, state, { scrollY: scrollY }); if (pushHistory) { + nextState.openedFromCard = openedFromCard; history.pushState(nextState, "", url); } else { history.replaceState(nextState, "", url); @@ -614,7 +660,10 @@ if (browserBack) { browserBack.addEventListener("click", function () { - if (browserBackAction) { + if (history.state && history.state.openedFromCard) { + saveCurrentScrollPosition(); + history.back(); + } else if (browserBackAction) { browserBackAction(); } }); @@ -633,15 +682,7 @@ browserBack.hidden = false; browserBackAction = function () { - var state = history.state; - if (state - && state.view === "folder" - && String(state.enteredFromFolderId || "") === String(parentFolderId)) { - saveCurrentScrollPosition(); - history.back(); - } else { - showFolder(String(parentFolderId), true); - } + showFolder(String(parentFolderId), true); }; } @@ -669,14 +710,25 @@ headers["X-Request-Id"] = id; } - return fetch(url, { headers: headers }).then(function (response) { + // a read must not pass a progress save that is on its way, or it gets the old progress + return progressSyncPromise.then(function () { + return fetch(url, { headers: headers }); + }).then(function (response) { if (!response.ok) { - throw new Error("Request failed with status " + response.status); + var error = new Error("Request failed with status " + response.status); + error.status = response.status; + throw error; } return response.json(); }); } + function notFoundError(message) { + var error = new Error(message); + error.status = 404; + return error; + } + function postJson(url, payload) { return sendJson("POST", url, payload); } @@ -721,8 +773,118 @@ return comicUrl(comicId) + "/read"; } + // the reading lists, smart lists, and tags sections have the same pages, with the routes and the words of each + // kind; tags have no folders + var listSections = { + reading_list: { + kind: "reading_list", + itemType: "reading_list", + itemWords: ["list", "lists"], + title: "Reading lists", + listTitle: "Reading list", + nameField: "reading_list_name", + artClass: "reading-list-art", + emptySection: ["No reading lists", "There are no reading lists in this library yet."], + emptyFolder: "There are no reading lists here yet.", + emptyList: ["This reading list is empty", "There are no comics in this list yet."] + }, + smart_list: { + kind: "smart_list", + itemType: "smart_list", + itemWords: ["list", "lists"], + title: "Smart lists", + listTitle: "Smart list", + nameField: "smart_list_name", + artClass: "smart-list-art", + emptySection: ["No smart lists", "There are no smart lists in this library yet."], + emptyFolder: "There are no smart lists here yet.", + emptyList: ["No comics", "No comics match the search of this list."] + }, + tag: { + kind: "tag", + itemType: "label", + itemWords: ["tag", "tags"], + title: "Tags", + listTitle: "Tag", + nameField: "label_name", + // the label art of the desktop in the color of the tag + artClass: "label-art", + artClassFor: function (label) { + return "label-art label-color-" + Number(label.color_id); + }, + emptySection: ["No tags", "There are no tags in this library yet."], + emptyFolder: "", + emptyList: ["This tag is empty", "There are no comics with this tag yet."] + } + }; + + // Favorites and Recent are one list of comics each, with no folders + var comicLists = { + favorites: { + view: "favorites", + title: "Favorites", + api: function () { + return "/v2/library/" + encodeURIComponent(libraryId) + "/favs"; + }, + summary: function (count) { + return countLabel(count, "comic", "comics"); + }, + emptyList: function () { + return ["No favorites", "There are no comics in Favorites yet."]; + } + }, + recent: { + view: "recent", + title: "Recent", + api: function () { + return v3Api("/recent?days=" + recentDays()); + }, + summary: function (count) { + return countLabel(count, "comic", "comics") + " · added in the last " + countLabel(recentDays(), "day", "days"); + }, + emptyList: function () { + return ["No recent comics", "No comics were added in the last " + countLabel(recentDays(), "day", "days") + "."]; + } + } + }; + + function listSectionUrl(section) { + return libraryUrl() + "/" + section.kind + "s"; + } + + function listFolderUrl(section, folderId) { + return libraryUrl() + "/" + section.kind + "_folder/" + encodeURIComponent(folderId); + } + + function listUrl(section, listId) { + return libraryUrl() + "/" + section.kind + "/" + encodeURIComponent(listId); + } + + // a missing comic is shown inside the content that has it ({ kind: "folder" or a list kind, id }); its key is + // the placeholder id in a folder and the entry id in a list + function missingComicUrl(container, key) { + var containerUrl = container.kind === "folder" + ? libraryUrl() + "/folder/" + encodeURIComponent(container.id) + : listUrl(listSections[container.kind], container.id); + return containerUrl + "/missing/" + encodeURIComponent(key); + } + + function missingComicKey(item, container) { + return String(container.kind === "folder" ? item.placeholder_id : item.entry_id); + } + + function v3Api(path) { + return "/v3/library/" + encodeURIComponent(libraryId) + path; + } + + function containerContentApi(container) { + return container.kind === "folder" + ? folderContentApi(container.id) + : v3Api("/" + container.kind + "/" + encodeURIComponent(container.id) + "/content"); + } + function folderContentApi(folderId) { - return "/v2/library/" + encodeURIComponent(libraryId) + "/folder/" + encodeURIComponent(folderId) + "/content"; + return "/v3/library/" + encodeURIComponent(libraryId) + "/folder/" + encodeURIComponent(folderId) + "/content"; } function continueReadingApi() { @@ -753,7 +915,7 @@ return "/v2/library/" + encodeURIComponent(libraryId) + "/search"; } - // PATCH these to change fields of an item (see YACReaderLibrary/server/API.md) + // PATCH these to change fields of an item function comicApi(comicId) { return "/v2/library/" + encodeURIComponent(libraryId) + "/comic/" + encodeURIComponent(comicId); } @@ -794,8 +956,12 @@ function coverUrl(item) { var filePath = ""; - if (item.type === "comic" && item.hash) { + if ((item.type === "comic" || item.type === "deleted_comic") && item.hash) { filePath = encodeURIComponent(item.hash) + ".jpg"; + } else if (item.type === "placeholder") { + filePath = safeCoverPath(item.cover); + } else if (item.type === "reading_list" && item.first_comic_hash) { + filePath = encodeURIComponent(item.first_comic_hash) + ".jpg"; } else if (item.type === "folder") { if (item.custom_image) { filePath = safeCoverPath(item.custom_image); @@ -808,24 +974,44 @@ return ""; } - return "/v2/library/" + encodeURIComponent(libraryId) + "/cover/" + filePath; + return "/v3/library/" + encodeURIComponent(libraryId) + "/cover/" + filePath; } - function readableComicTitle(comic) { - var number = comic.universal_number; - var title = comic.title && String(comic.title).trim(); - var volume = comic.volume && String(comic.volume).trim(); + function isMissingComic(item) { + return item.type === "deleted_comic" || item.type === "placeholder"; + } - if (number !== undefined && number !== null && String(number).trim() !== "") { - if (title) { - return "#" + number + " — " + title; - } - if (volume) { - return "#" + number + " — " + volume; - } + // Display labels only; never substitute these fallbacks into server metadata. + function readableComicTitle(item) { + var hasText = function (value) { + return value !== undefined && value !== null && String(value).trim() !== ""; + }; + if (hasText(item.title) && hasText(item.universal_number)) { + return "#" + item.universal_number + " — " + item.title; } - - return title || volume || comic.file_name || "Untitled comic"; + if (hasText(item.title)) { + return String(item.title); + } + if (hasText(item.series) && hasText(item.universal_number)) { + return item.series + " #" + item.universal_number; + } + if (hasText(item.series)) { + return String(item.series); + } + if (hasText(item.volume) && hasText(item.universal_number)) { + return "#" + item.universal_number + " — " + item.volume; + } + if (hasText(item.volume)) { + return String(item.volume); + } + var fileName = item.file_name || item.last_known_name; + if (hasText(fileName)) { + return String(fileName); + } + if (hasText(item.universal_number)) { + return "#" + item.universal_number; + } + return "-"; } function setPageHeading(title) { @@ -883,19 +1069,32 @@ showLoading(); } - function showError(retry) { + function showError(retry, error) { historyNavigationPending = false; historyTraversalPending = false; browserRoot.removeAttribute("aria-busy"); browserRoot.replaceChildren(); + var notFound = Boolean(error && error.status === 404); + var unreadable = Boolean(error && error.unreadable); + var title = notFound + ? "This item is no longer in the library" + : unreadable + ? "This comic cannot be opened" + : "Couldn’t load this content"; + var details = notFound + ? "It may have been moved or removed. Update the library if this is not correct." + : unreadable + ? "This comic has no readable pages." + : "The server could not read this content. The library may be updating or no longer available."; + var state = element("div", "browser-state"); var icon = element("div", "browser-state-icon", "!"); - var heading = element("h2", "", "Couldn’t load this library"); - var description = element("p", "", "The server could not read this content. The library may be updating or no longer available."); - var button = element("button", "primary-button", "Try again"); + var heading = element("h2", "", title); + var description = element("p", "", details); + var button = element("button", "primary-button", notFound || unreadable ? "Go to the library" : "Try again"); button.type = "button"; - button.addEventListener("click", retry); + button.addEventListener("click", notFound || unreadable ? function () { showFolder("1", true); } : retry); state.append(icon, heading, description, button); browserRoot.appendChild(state); @@ -1255,13 +1454,13 @@ } } - function actionMenuButton(className, label, entriesProvider) { + function actionMenuButton(className, label, entriesProvider, iconMarkup) { var button = element("button", className); button.type = "button"; button.setAttribute("aria-haspopup", "menu"); button.setAttribute("aria-expanded", "false"); button.setAttribute("aria-label", label); - button.appendChild(svgIcon("action-menu-button-icon", '')); + button.appendChild(svgIcon("action-menu-button-icon", iconMarkup || '')); button.addEventListener("click", function (event) { event.preventDefault(); event.stopPropagation(); @@ -1383,7 +1582,7 @@ } } - function folderCard(folder, containingFolderId) { + function folderCard(folder) { var card = element("a", "browser-card folder-card"); card.href = folderUrl(String(folder.id)); card.dataset.browserHistoryKey = "folder:" + String(folder.id); @@ -1392,7 +1591,7 @@ return; } event.preventDefault(); - showFolder(String(folder.id), true, containingFolderId, card); + showFolder(String(folder.id), true, card); }); var cover = element("div", "browser-cover folder-cover"); @@ -1421,7 +1620,7 @@ return withActionMenu(card, folder, function () { return folderMenuEntries(folder); }, function (updated, shell) { - replaceCard(shell, folderCard(updated, containingFolderId)); + replaceCard(shell, folderCard(updated)); }); } @@ -1439,7 +1638,7 @@ if (options.continueReading) { showReader(comicId, true, comic, card); } else { - showComic(comicId, true, card); + showComic(comicId, true, card, options.listContext); } }); @@ -1486,6 +1685,51 @@ }); } + // in grayscale, as in the desktop; the default cover when there is no cover file + function missingComicCover(item, className) { + var cover = element("div", className + " missing-comic-cover"); + var fallback = element("img", "browser-cover-image"); + fallback.alt = ""; + fallback.src = "/images/webui/unavailableCover.svg"; + cover.appendChild(fallback); + var url = coverUrl(item); + if (url) { + var image = element("img", "browser-cover-image"); + image.alt = ""; + image.loading = "lazy"; + image.src = url; + image.addEventListener("error", function () { + image.remove(); + }); + cover.appendChild(image); + } + return cover; + } + + function missingComicCard(item, container) { + var key = missingComicKey(item, container); + var card = element("a", "browser-card comic-card"); + card.href = missingComicUrl(container, key); + card.dataset.browserHistoryKey = "missing:" + key; + card.addEventListener("click", function (event) { + if (!shouldHandleInAppLink(event)) { + return; + } + event.preventDefault(); + showMissingComic(container, key, true, item, card); + }); + + var copy = element("div", "browser-card-copy"); + copy.appendChild(element("div", "browser-card-title", readableComicTitle(item))); + var numPages = Number(item.num_pages) || 0; + copy.appendChild(element("div", "browser-card-meta", numPages ? (numPages === 1 ? "1 page" : numPages + " pages") : "")); + + card.append(missingComicCover(item, "browser-cover comic-cover"), copy); + var shell = element("div", "browser-card-shell"); + shell.appendChild(card); + return shell; + } + function continueReadingShelf(comics) { var shelf = element("section", "continue-reading-shelf"); shelf.setAttribute("aria-labelledby", "continue-reading-title"); @@ -1610,13 +1854,13 @@ var url = libraryUrl() + "?q=" + encodeURIComponent(normalizedQuery); var state = { view: "search", query: normalizedQuery }; finishHistoryNavigation(state, url, pushHistory, version); - }).catch(function () { + }).catch(function (error) { if (version !== navigationVersion) { return; } showError(function () { showSearch(normalizedQuery, false); - }); + }, error); }); } @@ -1732,7 +1976,7 @@ return parts; } - function showFolder(folderId, pushHistory, enteredFromFolderId, returnToCard) { + function showFolder(folderId, pushHistory, returnToCard) { startHistoryNavigation(pushHistory, returnToCard); leaveReader(); setSearchVisible(folderId === "1"); @@ -1745,20 +1989,18 @@ Promise.all([ fetchJson(folderContentApi(folderId)), folderTrail(folderId), - folderId === "1" - ? progressSyncPromise.then(function () { return fetchJson(continueReadingApi()); }) - : Promise.resolve([]) + folderId === "1" ? fetchJson(continueReadingApi()) : Promise.resolve([]) ]).then(function (results) { if (version !== navigationVersion) { return; } - var items = sortBrowserItems(results[0]); + var items = results[0]; var trail = results[1]; var continueReading = results[2].filter(function (item) { return item.type === "comic"; }); var folderName = trail.length ? trail[trail.length - 1].name : libraryName; var folders = items.filter(function (item) { return item.type === "folder"; }); - var comics = items.filter(function (item) { return item.type === "comic"; }); + var comics = items.filter(function (item) { return item.type === "comic" || isMissingComic(item); }); var parentFolderId = folderId === "1" ? null : trail.length > 1 @@ -1777,6 +2019,9 @@ var titleRow = element("div", "browser-library-title-row"); titleRow.appendChild(element("h2", "", folderName)); var currentFolder = folderMetadataCache[folderId]; + if (folderId === "1") { + titleRow.appendChild(actionMenuButton("icon-button browser-header-menu-button", "Lists", libraryMenuEntries, '')); + } if (folderId !== "1" && currentFolder) { titleRow.appendChild(actionMenuButton("icon-button browser-header-menu-button", "Actions for " + folderName, function () { return folderMenuEntries(currentFolder); @@ -1807,9 +2052,11 @@ var grid = element("div", "browser-grid"); items.forEach(function (item) { if (item.type === "folder") { - grid.appendChild(folderCard(item, folderId)); + grid.appendChild(folderCard(item)); } else if (item.type === "comic") { grid.appendChild(comicCard(item)); + } else if (item.type === "placeholder") { + grid.appendChild(missingComicCard(item, { kind: "folder", id: folderId })); } }); browserRoot.appendChild(grid); @@ -1817,17 +2064,14 @@ var url = folderUrl(folderId); var state = { view: "folder", itemId: folderId }; - if (pushHistory && enteredFromFolderId) { - state.enteredFromFolderId = String(enteredFromFolderId); - } finishHistoryNavigation(state, url, pushHistory, version); - }).catch(function () { + }).catch(function (error) { if (version !== navigationVersion) { return; } showError(function () { showFolder(folderId, false); - }); + }, error); }); } @@ -1836,8 +2080,7 @@ } function fileTypeName(fileType) { - var names = ["Comic", "Manga", "Western manga", "Web comic", "Yonkoma"]; - return names[Number(fileType)] || ""; + return fileTypeNames[Number(fileType)] || ""; } function formatFileSize(value) { @@ -1956,6 +2199,7 @@ function sanitizedHtml(value) { var allowedElements = { A: true, + B: true, BLOCKQUOTE: true, BR: true, CODE: true, @@ -1967,6 +2211,7 @@ H5: true, H6: true, HR: true, + I: true, LI: true, OL: true, P: true, @@ -1979,6 +2224,7 @@ TH: true, THEAD: true, TR: true, + U: true, UL: true }; var discardedElements = { @@ -2062,9 +2308,29 @@ return result; } - function showReader(comicId, pushHistory, existingComic, returnToCard) { + function listContextFromHistory(comicId) { + var state = history.state; + return state && String(state.itemId) === String(comicId) && state.listContext || null; + } + + function showListOfContext(listContext) { + if (comicLists[listContext.view]) { + showComicList(comicLists[listContext.view], true); + } else { + showList(listSections[listContext.view], listContext.id, true); + } + } + + // options.listContext: the list that sets the previous and next comic, else the folder of the comic does; + // options.continued: opened from the previous or next comic, so the reader stays on the screen + function showReader(comicId, pushHistory, existingComic, returnToCard, options) { + options = options || {}; + var listContext = options.listContext || (pushHistory ? null : listContextFromHistory(comicId)); startHistoryNavigation(pushHistory, returnToCard); leaveReader(); + if (options.continued) { + document.body.classList.add("web-reader-active"); + } setSearchVisible(false); var version = ++navigationVersion; showNavigationLoading(); @@ -2077,10 +2343,12 @@ var title = readableComicTitle(comic); var numPages = Math.max(0, Number(comic.num_pages) || 0); if (!numPages) { - throw new Error("This comic has no readable pages."); + var unreadableError = new Error("This comic has no readable pages."); + unreadableError.unreadable = true; + throw unreadableError; } - var savedPage = Number(comic.current_page) || 1; + var savedPage = comic.read ? 1 : Number(comic.current_page) || 1; var currentPage = Math.min(numPages - 1, Math.max(0, savedPage - 1)); var requestedPage = currentPage; var pageRequestVersion = 0; @@ -2089,8 +2357,10 @@ var pageAbortControllers = Object.create(null); var destroyed = false; var hasDisplayedPage = false; - var progressSynced = false; + var lastSyncedPage = null; var comicOpened = false; + var neighbours = { previous: null, next: null }; + var comicOverlay = null; setPageHeading(title); setBrowserBack(null); @@ -2143,25 +2413,24 @@ function updateNavigation() { pageIndicator.textContent = (requestedPage + 1) + " / " + numPages; - previousButton.disabled = !comicOpened || requestedPage <= 0; - nextButton.disabled = !comicOpened || requestedPage >= numPages - 1; + previousButton.disabled = !comicOpened || (requestedPage <= 0 && !neighbours.previous); + nextButton.disabled = !comicOpened || (requestedPage >= numPages - 1 && !neighbours.next); } function syncProgress() { - if (!hasDisplayedPage || progressSynced) { - return progressSyncPromise; + if (!hasDisplayedPage || lastSyncedPage === currentPage) { + return; } - progressSynced = true; + lastSyncedPage = currentPage; var headers = apiHeaders("text/plain"); headers["Content-Type"] = "text/plain; charset=utf-8"; progressSyncPromise = fetch(comicProgressApi(comicId), { method: "POST", headers: headers, - body: "currentPage:" + (currentPage + 1) + "\n", + body: "currentPage:" + (currentPage + 1) + "\n" + (currentPage === numPages - 1 && neighbours.next ? neighbours.next.id : ""), keepalive: true }).catch(function () { }); - return progressSyncPromise; } function cancelledPageError() { @@ -2301,6 +2570,123 @@ }); } + function neighboursFromOpenResponse(text) { + var values = {}; + text.split(/\r?\n/).forEach(function (line) { + var separator = line.indexOf(":"); + var key = line.slice(0, separator); + if (separator > 0 && !(key in values)) { + values[key] = line.slice(separator + 1); + } + }); + return { + previous: values.previousComic ? { id: values.previousComic, hash: values.previousComicHash } : null, + next: values.nextComic ? { id: values.nextComic, hash: values.nextComicHash } : null + }; + } + + function loadNeighboursFromList() { + fetchJson(listContext.api).then(function (items) { + if (destroyed) { + return; + } + var comics = items.filter(function (item) { return item.type === "comic"; }); + var position = listContext.position; + // a comic can be in a list more than once, so its position comes first + if (!comics[position] || String(comics[position].id) !== String(comicId)) { + position = comics.findIndex(function (item) { return String(item.id) === String(comicId); }); + } + if (position < 0) { + return; + } + function neighbourAt(index) { + var item = comics[index]; + return item ? { id: item.id, hash: item.hash, position: index } : null; + } + neighbours = { previous: neighbourAt(position - 1), next: neighbourAt(position + 1) }; + updateNavigation(); + }).catch(function () { + }); + } + + function openNeighbour(direction) { + var neighbour = neighbours[direction]; + showReader(String(neighbour.id), false, null, null, { + listContext: listContext ? Object.assign({}, listContext, { position: neighbour.position }) : null, + continued: true + }); + } + + function closeComicOverlay() { + if (comicOverlay) { + comicOverlay.element.remove(); + comicOverlay = null; + } + } + + function showComicOverlay(direction) { + closeComicOverlay(); + + var overlay = element("div", "reader-comic-overlay " + direction); + overlay.addEventListener("click", function (event) { + if (event.target === overlay) { + closeComicOverlay(); + } + }); + + var coverButton = element("button", "reader-comic-overlay-cover"); + coverButton.type = "button"; + coverButton.setAttribute("aria-label", direction === "next" ? "Open the next comic" : "Open the previous comic"); + coverButton.addEventListener("click", function () { + openNeighbour(direction); + }); + var cover = element("img"); + cover.alt = ""; + cover.addEventListener("error", function () { + cover.src = "/images/webui/unavailableCover.svg"; + }, { once: true }); + cover.src = coverUrl({ type: "comic", hash: neighbours[direction].hash }) || "/images/webui/unavailableCover.svg"; + coverButton.appendChild(cover); + + var closeButton = element("button", "reader-comic-overlay-close", "Close"); + closeButton.type = "button"; + closeButton.addEventListener("click", exitReader); + + overlay.append(coverButton, closeButton); + stage.appendChild(overlay); + comicOverlay = { element: overlay, direction: direction }; + coverButton.focus({ preventScroll: true }); + } + + function goBeyondComic(direction) { + if (!neighbours[direction]) { + return; + } + if (comicOverlay && comicOverlay.direction === direction) { + openNeighbour(direction); + } else { + showComicOverlay(direction); + } + } + + function goToPrevious() { + if (requestedPage > 0) { + closeComicOverlay(); + loadPage(requestedPage - 1); + } else { + goBeyondComic("previous"); + } + } + + function goToNext() { + if (requestedPage < numPages - 1) { + closeComicOverlay(); + loadPage(requestedPage + 1); + } else { + goBeyondComic("next"); + } + } + function openComicAndLoad() { errorState.hidden = true; loading.hidden = false; @@ -2312,9 +2698,16 @@ if (!response.ok) { throw new Error("Comic request failed with status " + response.status); } + return response.text(); + }).then(function (text) { if (destroyed) { return; } + if (listContext) { + loadNeighboursFromList(); + } else { + neighbours = neighboursFromOpenResponse(text); + } comicOpened = true; loadPage(requestedPage); }).catch(function () { @@ -2338,34 +2731,92 @@ } function handleReaderKeys(event) { - if (event.key === "ArrowLeft") { + if (event.altKey || event.ctrlKey || event.metaKey || document.body.classList.contains("mobile-promotion-open")) { + return; + } + if (event.key === "ArrowLeft" || event.key === "PageUp") { + event.preventDefault(); + goToPrevious(); + } else if (event.key === "ArrowRight" || event.key === "PageDown" || event.key === " ") { + event.preventDefault(); + goToNext(); + } else if (event.key === "Home") { event.preventDefault(); + closeComicOverlay(); if (requestedPage > 0) { - loadPage(requestedPage - 1); + loadPage(0); } - } else if (event.key === "ArrowRight" || event.key === " ") { + } else if (event.key === "End") { event.preventDefault(); + closeComicOverlay(); if (requestedPage < numPages - 1) { - loadPage(requestedPage + 1); + loadPage(numPages - 1); } } else if (event.key === "Escape") { event.preventDefault(); - exitReader(); + if (comicOverlay) { + closeComicOverlay(); + } else { + exitReader(); + } + } + } + + var wheelDelta = 0; + var wheelLocked = false; + var wheelTurnTime = 0; + var wheelIdleTimer = null; + + function handleReaderWheel(event) { + if (event.ctrlKey) { + return; + } + event.preventDefault(); + var delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY; + if (event.deltaMode === 1) { + delta *= 40; + } else if (event.deltaMode === 2) { + delta *= 800; + } + var discreteStep = event.deltaMode !== 0 || Math.abs(delta) >= 100; + + clearTimeout(wheelIdleTimer); + wheelIdleTimer = setTimeout(function () { + wheelLocked = false; + wheelDelta = 0; + }, 200); + + if (wheelLocked) { + if (!discreteStep || Date.now() - wheelTurnTime < 250) { + return; + } + wheelLocked = false; + wheelDelta = 0; + } + + wheelDelta += delta; + if (Math.abs(wheelDelta) < 50) { + return; + } + var forward = wheelDelta > 0; + wheelDelta = 0; + wheelLocked = true; + wheelTurnTime = Date.now(); + if (forward) { + goToNext(); + } else { + goToPrevious(); } } - function handleReaderPageHide(event) { - if (!event.persisted) { + function handleReaderVisibilityChange() { + if (document.visibilityState === "hidden") { syncProgress(); } } - previousButton.addEventListener("click", function () { - loadPage(requestedPage - 1); - }); - nextButton.addEventListener("click", function () { - loadPage(requestedPage + 1); - }); + previousButton.addEventListener("click", goToPrevious); + nextButton.addEventListener("click", goToNext); retryButton.addEventListener("click", function () { if (comicOpened) { loadPage(requestedPage); @@ -2375,7 +2826,9 @@ }); exitButton.addEventListener("click", exitReader); document.addEventListener("keydown", handleReaderKeys); - window.addEventListener("pagehide", handleReaderPageHide); + stage.addEventListener("wheel", handleReaderWheel, { passive: false }); + document.addEventListener("visibilitychange", handleReaderVisibilityChange); + window.addEventListener("pagehide", syncProgress); readerCleanup = function () { syncProgress(); @@ -2390,30 +2843,38 @@ URL.revokeObjectURL(pageCache[page]); }); document.removeEventListener("keydown", handleReaderKeys); - window.removeEventListener("pagehide", handleReaderPageHide); + clearTimeout(wheelIdleTimer); + document.removeEventListener("visibilitychange", handleReaderVisibilityChange); + window.removeEventListener("pagehide", syncProgress); }; var existingReaderState = !pushHistory && history.state && history.state.view === "reader" ? Boolean(history.state.fromComicDetail) : false; var url = readerUrl(comicId); - var state = { view: "reader", itemId: comicId, fromComicDetail: pushHistory || existingReaderState }; + var state = { + view: "reader", + itemId: comicId, + fromComicDetail: !options.continued && (pushHistory || existingReaderState), + listContext: listContext + }; finishHistoryNavigation(state, url, pushHistory, version); updateNavigation(); openComicAndLoad(); - }).catch(function () { + }).catch(function (error) { if (version !== navigationVersion) { return; } leaveReader(); showError(function () { showReader(comicId, false); - }); + }, error); }); } - function showComic(comicId, pushHistory, returnToCard) { + function showComic(comicId, pushHistory, returnToCard, listContext) { + listContext = listContext || (pushHistory ? null : listContextFromHistory(comicId)); startHistoryNavigation(pushHistory, returnToCard); leaveReader(); setSearchVisible(false); @@ -2436,7 +2897,11 @@ var parentId = String(comic.parent_id || "1"); setPageHeading(title); - setBrowserBack(parentId); + if (listContext) { + setBrowserBackTo(function () { showListOfContext(listContext); }); + } else { + setBrowserBack(parentId); + } renderBreadcrumbs(browserBreadcrumbParts(trail, title)); browserRoot.removeAttribute("aria-busy"); @@ -2459,7 +2924,7 @@ var read = element("button", "secondary-button comic-read-button", "Read"); read.type = "button"; read.addEventListener("click", function () { - showReader(comicId, true, comic); + showReader(comicId, true, comic, null, { listContext: listContext }); }); coverColumn.appendChild(read); @@ -2663,65 +3128,596 @@ browserRoot.appendChild(detail); var url = comicUrl(comicId); - var state = { view: "comic", itemId: comicId }; + var state = { view: "comic", itemId: comicId, listContext: listContext }; finishHistoryNavigation(state, url, pushHistory, version); - }).catch(function () { + }).catch(function (error) { if (version !== navigationVersion) { return; } showError(function () { showComic(comicId, false); - }); + }, error); }); } - function routeFromLocation() { - var query = new URL(window.location.href).searchParams.get("q"); - if (query && query.trim()) { - return { view: "search", query: query.trim() }; - } - var escapedLibraryId = libraryId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - var match = window.location.pathname.match(new RegExp("^/webui/library/" + escapedLibraryId + "(?:/(folder|comic)/([0-9]+)(?:/(read))?)?/?$")); - if (!match) { - return { view: "folder", itemId: "1" }; - } - return { - view: match[3] ? "reader" : match[1] || "folder", - itemId: match[2] || "1" - }; + // the lists of the library, as in the apps + function libraryMenuEntries() { + return [ + { + label: "Reading lists", + run: function () { + showListSection(listSections.reading_list, null, true); + } + }, + { + label: "Smart lists", + run: function () { + showListSection(listSections.smart_list, null, true); + } + }, + { + label: "Favorites", + run: function () { + showComicList(comicLists.favorites, true); + } + }, + { + label: "Tags", + run: function () { + showListSection(listSections.tag, null, true); + } + }, + { + label: "Recent", + run: function () { + showComicList(comicLists.recent, true); + } + } + ]; } - function showRoute(route) { - if (route.view === "search") { - showSearch(route.query, false); - } else if (route.view === "reader") { - showReader(route.itemId, false); - } else if (route.view === "comic") { - showComic(route.itemId, false); - } else { - showFolder(route.itemId, false); + function setBrowserBackTo(show) { + if (!browserBack) { + return; } + browserBack.hidden = false; + browserBackAction = show; } - // Loads the current view again from the server. The old content stays visible - // while it loads, and the scroll position is kept. - function reloadCurrentView() { - var route = routeFromLocation(); - if (route.view === "reader") { - return; + var listFolderCache = {}; + + // the list folders from the top of the section down to folderId + function listFolderTrail(section, folderId) { + var trail = []; + var visited = {}; + + function load(currentId) { + if (!currentId || visited[currentId]) { + return Promise.resolve(trail); + } + visited[currentId] = true; + + var path = "/" + section.kind + "_folder/" + encodeURIComponent(currentId); + var cached = listFolderCache[path]; + return (cached ? Promise.resolve(cached) : fetchJson(v3Api(path))).then(function (folder) { + listFolderCache[path] = folder; + trail.unshift({ + name: folder.folder_name || "Untitled folder", + href: listFolderUrl(section, currentId), + action: function () { + showListSection(section, currentId, true); + } + }); + return load(folder.parent_id ? String(folder.parent_id) : ""); + }); } - saveCurrentScrollPosition(); - historyNavigationPending = true; - historyTraversalPending = true; - showRoute(route); + return load(folderId ? String(folderId) : ""); } - window.addEventListener("popstate", function () { - historyNavigationPending = true; - historyTraversalPending = Boolean(history.state - && (history.state.scrollAnchorKey - || (Number.isFinite(history.state.scrollY) && history.state.scrollY > 0))); + // the folders of the list, then the list + function listTrail(section, listId) { + var loadList = section.kind === "tag" + ? fetchJson(v3Api("/tags")).then(function (labels) { + var label = labels.filter(function (item) { return String(item.id) === String(listId); })[0]; + if (!label) { + throw notFoundError("tag not found"); + } + return label; + }) + : fetchJson(v3Api("/" + section.kind + "/" + encodeURIComponent(listId))); + return loadList.then(function (list) { + return listFolderTrail(section, list.folder_id).then(function (trail) { + return trail.concat([{ + name: list[section.nameField] || "Untitled list", + list: list, + href: listUrl(section, listId), + action: function () { + showList(section, listId, true); + } + }]); + }); + }); + } + + function listBreadcrumbParts(section, trail, finalLabel) { + var parts = [{ + label: "Libraries", + href: "/webui#libraries" + }, { + label: libraryName, + href: libraryUrl(), + action: function () { + showFolder("1", true); + } + }]; + + if (trail.length === 0 && !finalLabel) { + parts.push({ label: section.title }); + return parts; + } + + parts.push({ + label: section.title, + href: listSectionUrl(section), + action: function () { + showListSection(section, null, true); + } + }); + trail.forEach(function (entry, index) { + var isLast = index === trail.length - 1 && !finalLabel; + parts.push({ + label: entry.name, + href: isLast ? "" : entry.href, + action: isLast ? null : entry.action + }); + }); + if (finalLabel) { + parts.push({ label: finalLabel }); + } + return parts; + } + + // the folder shape of the desktop, with no image + function listFolderCard(section, folder) { + var folderId = String(folder.id); + var card = element("a", "browser-card folder-card"); + card.href = listFolderUrl(section, folderId); + card.dataset.browserHistoryKey = section.kind + "_folder:" + folderId; + card.addEventListener("click", function (event) { + if (!shouldHandleInAppLink(event)) { + return; + } + event.preventDefault(); + showListSection(section, folderId, true, card); + }); + + var cover = element("div", "browser-cover folder-cover"); + cover.appendChild(element("div", "folder-cover-back")); + cover.appendChild(element("div", "folder-cover-middle")); + var front = element("div", "folder-cover-front"); + front.appendChild(folderPlaceholder()); + cover.appendChild(front); + + var copy = element("div", "browser-card-copy"); + copy.appendChild(element("div", "browser-card-title", folder.folder_name || "Untitled folder")); + card.append(cover, copy); + var shell = element("div", "browser-card-shell"); + shell.appendChild(card); + return shell; + } + + // a reading list shows the cover of its first comic, else the art of the desktop; a smart list shows its art + function listCard(section, list) { + var listId = String(list.id); + var card = element("a", "browser-card list-card"); + card.href = listUrl(section, listId); + card.dataset.browserHistoryKey = section.kind + ":" + listId; + card.addEventListener("click", function (event) { + if (!shouldHandleInAppLink(event)) { + return; + } + event.preventDefault(); + showList(section, listId, true, card); + }); + + var cover = element("div", "browser-cover list-cover"); + var art = element("div", "list-art " + (section.artClassFor ? section.artClassFor(list) : section.artClass)); + cover.appendChild(art); + var url = coverUrl(list); + if (url) { + art.hidden = true; + var image = element("img", "browser-cover-image"); + image.alt = ""; + image.loading = "lazy"; + image.src = url; + image.addEventListener("error", function () { + image.remove(); + art.hidden = false; + }); + cover.appendChild(image); + } + + var copy = element("div", "browser-card-copy"); + copy.appendChild(element("div", "browser-card-title", list[section.nameField] || "Untitled list")); + card.append(cover, copy); + var shell = element("div", "browser-card-shell"); + shell.appendChild(card); + return shell; + } + + function pageHeader(sectionTitle, title, summary) { + var header = element("section", "browser-library-header"); + header.appendChild(element("div", "section-title", sectionTitle)); + var titleRow = element("div", "browser-library-title-row"); + titleRow.appendChild(element("h2", "", title)); + header.appendChild(titleRow); + header.appendChild(element("p", "", summary)); + return header; + } + + function emptyState(title, description, iconClass) { + var empty = element("div", "browser-state compact"); + empty.appendChild(element("div", "browser-state-icon " + (iconClass || "folder-state-icon"))); + empty.appendChild(element("h2", "", title)); + empty.appendChild(element("p", "", description)); + return empty; + } + + function countLabel(count, singular, plural) { + return count === 1 ? "1 " + singular : count + " " + plural; + } + + // a lists section (folderId: nothing) or one of its folders: its folders, then its lists + function showListSection(section, folderId, pushHistory, returnToCard) { + startHistoryNavigation(pushHistory, returnToCard); + leaveReader(); + setSearchVisible(false); + var version = ++navigationVersion; + showNavigationLoading(); + + Promise.all([ + fetchJson(folderId ? v3Api("/" + section.kind + "_folder/" + encodeURIComponent(folderId) + "/content") : v3Api("/" + section.kind + "s")), + listFolderTrail(section, folderId) + ]).then(function (results) { + if (version !== navigationVersion) { + return; + } + + var items = results[0]; + var trail = results[1]; + var name = trail.length ? trail[trail.length - 1].name : section.title; + var folders = items.filter(function (item) { return item.type === section.kind + "_folder"; }); + var lists = items.filter(function (item) { return item.type === section.itemType; }); + + setPageHeading(name); + setBrowserBackTo(trail.length > 1 + ? trail[trail.length - 2].action + : folderId + ? function () { showListSection(section, null, true); } + : function () { showFolder("1", true); }); + renderBreadcrumbs(listBreadcrumbParts(section, trail)); + + browserRoot.removeAttribute("aria-busy"); + browserRoot.replaceChildren(); + + var summaryParts = []; + if (folders.length) { + summaryParts.push(countLabel(folders.length, "folder", "folders")); + } + if (lists.length) { + summaryParts.push(countLabel(lists.length, section.itemWords[0], section.itemWords[1])); + } + browserRoot.appendChild(pageHeader(folderId ? "Folder" : "Library", name, summaryParts.join(" · ") || "No items")); + + if (!items.length) { + browserRoot.appendChild(folderId + ? emptyState("This folder is empty", section.emptyFolder) + : emptyState(section.emptySection[0], section.emptySection[1])); + } else { + var grid = element("div", "browser-grid"); + folders.forEach(function (folder) { + grid.appendChild(listFolderCard(section, folder)); + }); + lists.forEach(function (list) { + grid.appendChild(listCard(section, list)); + }); + browserRoot.appendChild(grid); + } + + var state = { view: folderId ? section.kind + "_folder" : section.kind + "s", itemId: folderId || "" }; + finishHistoryNavigation(state, folderId ? listFolderUrl(section, folderId) : listSectionUrl(section), pushHistory, version); + }).catch(function (error) { + if (version !== navigationVersion) { + return; + } + showError(function () { + showListSection(section, folderId, false); + }, error); + }); + } + + // the comics of a list, in the order of the desktop + function showList(section, listId, pushHistory, returnToCard) { + startHistoryNavigation(pushHistory, returnToCard); + leaveReader(); + setSearchVisible(false); + var version = ++navigationVersion; + showNavigationLoading(); + + Promise.all([ + fetchJson(v3Api("/" + section.kind + "/" + encodeURIComponent(listId) + "/content")), + listTrail(section, listId) + ]).then(function (results) { + if (version !== navigationVersion) { + return; + } + + var items = results[0]; + var trail = results[1]; + var name = trail[trail.length - 1].name; + var container = { kind: section.kind, id: listId }; + + setPageHeading(name); + setBrowserBackTo(trail.length > 1 + ? trail[trail.length - 2].action + : function () { showListSection(section, null, true); }); + renderBreadcrumbs(listBreadcrumbParts(section, trail)); + + browserRoot.removeAttribute("aria-busy"); + browserRoot.replaceChildren(); + browserRoot.appendChild(pageHeader(section.listTitle, name, countLabel(items.length, "comic", "comics"))); + + if (!items.length) { + var list = trail[trail.length - 1].list; + browserRoot.appendChild(emptyState(section.emptyList[0], section.emptyList[1], + section.artClassFor ? "list-state-icon " + section.artClassFor(list) : "")); + } else { + var grid = element("div", "browser-grid"); + var listContext = { view: section.kind, id: listId, api: v3Api("/" + section.kind + "/" + encodeURIComponent(listId) + "/content") }; + var comicPosition = 0; + items.forEach(function (item) { + if (item.type === "comic") { + grid.appendChild(comicCard(item, { listContext: Object.assign({}, listContext, { position: comicPosition++ }) })); + } else if (isMissingComic(item)) { + grid.appendChild(missingComicCard(item, container)); + } + }); + browserRoot.appendChild(grid); + } + + finishHistoryNavigation({ view: section.kind, itemId: listId }, listUrl(section, listId), pushHistory, version); + }).catch(function (error) { + if (version !== navigationVersion) { + return; + } + showError(function () { + showList(section, listId, false); + }, error); + }); + } + + function showComicList(list, pushHistory, returnToCard) { + startHistoryNavigation(pushHistory, returnToCard); + leaveReader(); + setSearchVisible(false); + var version = ++navigationVersion; + showNavigationLoading(); + + fetchJson(list.api()).then(function (items) { + if (version !== navigationVersion) { + return; + } + + var comics = items.filter(function (item) { return item.type === "comic"; }); + + setPageHeading(list.title); + setBrowserBackTo(function () { showFolder("1", true); }); + renderBreadcrumbs(listBreadcrumbParts(list, [])); + + browserRoot.removeAttribute("aria-busy"); + browserRoot.replaceChildren(); + browserRoot.appendChild(pageHeader("Library", list.title, list.summary(comics.length))); + + if (!comics.length) { + var empty = list.emptyList(); + browserRoot.appendChild(emptyState(empty[0], empty[1])); + } else { + var grid = element("div", "browser-grid"); + var listContext = { view: list.view, id: "", api: list.api() }; + comics.forEach(function (comic, position) { + grid.appendChild(comicCard(comic, { listContext: Object.assign({}, listContext, { position: position }) })); + }); + browserRoot.appendChild(grid); + } + + finishHistoryNavigation({ view: list.view, itemId: "" }, libraryUrl() + "/" + list.view, pushHistory, version); + }).catch(function (error) { + if (version !== navigationVersion) { + return; + } + showError(function () { + showComicList(list, false); + }, error); + }); + } + + // what the item has, from the content that has it; there is no file to read + function showMissingComic(container, key, pushHistory, existingItem, returnToCard) { + startHistoryNavigation(pushHistory, returnToCard); + leaveReader(); + setSearchVisible(false); + var version = ++navigationVersion; + showNavigationLoading(); + + Promise.all([ + existingItem + ? Promise.resolve(existingItem) + : fetchJson(containerContentApi(container)).then(function (items) { + var found = items.filter(function (item) { + return isMissingComic(item) && missingComicKey(item, container) === key; + })[0]; + if (!found) { + throw notFoundError("missing comic not found"); + } + return found; + }), + container.kind === "folder" ? folderTrail(container.id) : listTrail(listSections[container.kind], container.id) + ]).then(function (results) { + if (version !== navigationVersion) { + return; + } + + var item = results[0]; + var trail = results[1]; + var title = readableComicTitle(item); + + setPageHeading(title); + if (container.kind === "folder") { + setBrowserBack(container.id); + renderBreadcrumbs(browserBreadcrumbParts(trail, title)); + } else { + var section = listSections[container.kind]; + setBrowserBackTo(function () { + showList(section, container.id, true); + }); + renderBreadcrumbs(listBreadcrumbParts(section, trail, title)); + } + + browserRoot.removeAttribute("aria-busy"); + browserRoot.replaceChildren(); + + var detail = element("article", "comic-detail"); + var coverColumn = element("div", "comic-detail-cover-column"); + coverColumn.appendChild(missingComicCover(item, "comic-detail-cover")); + + var copy = element("div", "comic-detail-copy"); + copy.appendChild(element("div", "section-title", "Comic information")); + var titleRow = element("div", "comic-detail-title-row"); + titleRow.appendChild(element("h2", "", title)); + copy.appendChild(titleRow); + if (item.last_known_name && item.last_known_name !== title) { + copy.appendChild(element("p", "comic-file-name", item.last_known_name)); + } + copy.appendChild(element("p", "missing-comic-note", "This comic is missing from your library.")); + + var facts = element("div", "comic-facts"); + var numPages = Number(item.num_pages) || 0; + if (numPages) { + facts.appendChild(element("span", "comic-fact", numPages === 1 ? "1 page" : numPages + " pages")); + } + if (item.read) { + facts.appendChild(element("span", "comic-fact success", "Read")); + } else if (Number(item.current_page) > 1) { + facts.appendChild(element("span", "comic-fact accent", "Page " + item.current_page)); + } + var fileType = item.type === "deleted_comic" ? fileTypeName(item.file_type) : ""; + if (fileType) { + facts.appendChild(element("span", "comic-fact", fileType)); + } + if (facts.childNodes.length) { + copy.appendChild(facts); + } + + var metadata = [ + ["Series", item.series], + ["Issue", item.universal_number], + ["Volume", item.volume] + ].filter(function (entry) { + return hasValue(entry[1]); + }); + if (metadata.length) { + var metadataSection = element("section", "comic-copy-section"); + metadataSection.appendChild(element("h3", "", "Metadata")); + var metadataList = element("dl", "comic-metadata-grid"); + metadata.forEach(function (entry) { + metadataList.appendChild(detailField(entry[0], entry[1])); + }); + metadataSection.appendChild(metadataList); + copy.appendChild(metadataSection); + } + + detail.append(coverColumn, copy); + browserRoot.appendChild(detail); + + var state = { view: "missing", itemId: container.kind + ":" + container.id, missingKey: key }; + finishHistoryNavigation(state, missingComicUrl(container, key), pushHistory, version); + }).catch(function (error) { + if (version !== navigationVersion) { + return; + } + showError(function () { + showMissingComic(container, key, false); + }, error); + }); + } + + function routeFromLocation() { + var query = new URL(window.location.href).searchParams.get("q"); + if (query && query.trim()) { + return { view: "search", query: query.trim() }; + } + var escapedLibraryId = libraryId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + var match = window.location.pathname.match(new RegExp("^/webui/library/" + escapedLibraryId + + "(?:/(folder|comic|reading_list_folder|reading_list|smart_list_folder|smart_list|tag)/([0-9]+)(?:/read|/missing/([0-9]+))?|/(reading_lists|smart_lists|tags|favorites|recent))?/?$")); + if (!match) { + return { view: "folder", itemId: "1" }; + } + if (match[4]) { + return { view: match[4], itemId: "" }; + } + if (match[3]) { + return { view: "missing", itemId: match[1] + ":" + match[2], missingKey: match[3] }; + } + var reader = /\/read\/?$/.test(window.location.pathname); + return { + view: reader ? "reader" : match[1] || "folder", + itemId: match[2] || "1" + }; + } + + function showRoute(route) { + if (route.view === "search") { + showSearch(route.query, false); + } else if (route.view === "reader") { + showReader(route.itemId, false); + } else if (route.view === "comic") { + showComic(route.itemId, false); + } else if (route.view === "missing") { + var separator = route.itemId.indexOf(":"); + showMissingComic({ kind: route.itemId.slice(0, separator), id: route.itemId.slice(separator + 1) }, route.missingKey, false); + } else if (comicLists[route.view]) { + showComicList(comicLists[route.view], false); + } else if (route.view === "reading_lists" || route.view === "smart_lists" || route.view === "tags") { + showListSection(listSections[route.view.slice(0, -1)], null, false); + } else if (route.view === "reading_list_folder" || route.view === "smart_list_folder") { + showListSection(listSections[route.view.slice(0, -"_folder".length)], route.itemId, false); + } else if (route.view === "reading_list" || route.view === "smart_list" || route.view === "tag") { + showList(listSections[route.view], route.itemId, false); + } else { + showFolder(route.itemId, false); + } + } + + // Loads the current view again from the server. The old content stays visible + // while it loads, and the scroll position is kept. + function reloadCurrentView() { + var route = routeFromLocation(); + if (route.view === "reader") { + return; + } + + saveCurrentScrollPosition(); + historyNavigationPending = true; + historyTraversalPending = true; + showRoute(route); + } + + window.addEventListener("popstate", function () { + historyNavigationPending = true; + historyTraversalPending = Boolean(history.state + && (history.state.scrollAnchorKey + || (Number.isFinite(history.state.scrollY) && history.state.scrollY > 0))); showRoute(routeFromLocation()); }); @@ -2734,8 +3730,10 @@ showReader(initialItemId, false); } else if (initialView === "comic") { showComic(initialItemId, false); - } else { + } else if (initialView === "folder") { showFolder(initialItemId, false); + } else { + showRoute(initialRoute); } } @@ -2805,13 +3803,61 @@ var polling = false; var active = null; // null | "all" | the card being updated + var noticeActions = Array.prototype.slice.call(container.querySelectorAll("[data-lock-notice-action]")); function setTriggersDisabled(disabled) { - triggers.forEach(function (trigger) { + triggers.concat(noticeActions).forEach(function (trigger) { trigger.disabled = disabled; }); } + function operationUrl(libraryId, operation) { + return "/v2/library/" + encodeURIComponent(libraryId) + (operation === "rescan-xml" ? "/rescan-xml" : "/update"); + } + + // A "!" button on a card explains why its last update or XML rescan could not + // take the library lock, and offers a retry or, for a stale lock, a recovery. + function applyLockProblems(problems) { + cards.forEach(function (card) { + var notice = card.querySelector("[data-lock-notice]"); + if (!notice) { + return; + } + var libraryId = card.getAttribute("data-library-id"); + var problem = (problems || []).filter(function (item) { + return String(item.library_id) === libraryId; + })[0]; + if (!problem) { + if (!notice.hidden && menus) { + menus.closeAll(null); + } + notice.hidden = true; + return; + } + var stale = problem.state === "stale"; + var operation = problem.operation === "rescan-xml" ? "XML rescan" : "update"; + notice.querySelector("[data-lock-notice-title]").textContent = stale ? "Library locked" : "Library busy"; + notice.querySelector("[data-lock-notice-text]").textContent = stale + ? "An earlier operation did not release this library, so the " + operation + " did not start. This can happen after a crash or a power cut." + : "Another operation is using this library, so the " + operation + " did not start. Wait for it to finish, then retry."; + notice.querySelector("[data-lock-notice-warning]").hidden = !stale; + var owner = notice.querySelector("[data-lock-notice-owner]"); + owner.textContent = problem.owner ? (stale ? "Last owner: " : "Owner: ") + problem.owner : ""; + owner.hidden = !problem.owner; + notice.querySelector("[data-lock-notice-error]").hidden = true; + var action = notice.querySelector("[data-lock-notice-action]"); + action.textContent = (stale ? "Recover and retry " : "Retry ") + operation; + action.problem = problem; + notice.hidden = false; + }); + } + + function showNoticeError(card, message) { + var error = card.querySelector("[data-lock-notice-error]"); + error.textContent = message; + error.hidden = false; + } + function showIndicators(target) { // "all": spin the header button only (per-card bars in lock-step add no // information). A single card: run the ping-pong bar on that card alone. @@ -2859,12 +3905,13 @@ } function checkStatus() { - return fetch("/v2/libraries/update/status", { headers: { "Accept": "application/json" } }) + return fetch("/v3/libraries/maintenance", { headers: { "Accept": "application/json" } }) .then(function (response) { return response.ok ? response.json() : { running: false }; }) .then(function (data) { applyRunning(!!(data && data.running)); + applyLockProblems(data && data.problems); }); } @@ -2921,13 +3968,165 @@ }); }); + noticeActions.forEach(function (button) { + var card = button.closest("[data-library-card]"); + var popover = button.closest("[data-menu-popover]"); + popover.addEventListener("click", function (event) { + event.stopPropagation(); + }); + button.addEventListener("click", function () { + var problem = button.problem; + if (button.disabled || !problem) { + return; + } + var url = operationUrl(problem.library_id, problem.operation); + if (problem.state !== "stale") { + trigger(card, url); + return; + } + setTriggersDisabled(true); + fetch("/v3/library/" + encodeURIComponent(problem.library_id) + "/maintenance/recover", { + method: "POST", + headers: { "Accept": "application/json", "Content-Type": "application/json" }, + body: JSON.stringify({ recovery_token: problem.recovery_token }) + }) + .then(function (response) { + if (response.ok) { + trigger(card, url); + return; + } + return response.json().catch(function () { return {}; }).then(function (data) { + setTriggersDisabled(false); + if (data.status === "not_recoverable") { + // the server now reports the current owner; let the user review it first + return checkStatus().then(function () { + showNoticeError(card, "The library lock owner changed. Check the new owner before you recover."); + }); + } + showNoticeError(card, data.error + ? "Recovery failed: " + data.error + : "The library is busy now. Retry when the current operation finishes."); + }); + }) + .catch(function () { + setTriggersDisabled(false); + showNoticeError(card, "The server did not answer. Check the connection and retry."); + }); + }); + }); + // Reflect an update that may already be running (started elsewhere or before reload). checkStatus().catch(function () {}); } + // "Connect a device" dialog on the home page: QR codes are rendered by the server + // into a