mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-01-05 21:08:06 -05:00
Compare commits
33 Commits
more_predi
...
13.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddde6db16f | ||
|
|
50d1394a0a | ||
|
|
a6040b2ecd | ||
|
|
4e755bc949 | ||
|
|
cfab4c946a | ||
|
|
57a265f73c | ||
|
|
3f945813f2 | ||
|
|
86100b39ed | ||
|
|
b2ae6d1fca | ||
|
|
e82b62c552 | ||
|
|
5fba3f434e | ||
|
|
3ac36e8ebd | ||
|
|
1babbc0e4a | ||
|
|
6b05eeb24b | ||
|
|
73b855ce6b | ||
|
|
eaca7010bc | ||
|
|
6efdc43964 | ||
|
|
7a0ab3a429 | ||
|
|
3e9d50fecb | ||
|
|
f3a604380c | ||
|
|
167e0dc4b3 | ||
|
|
14c9530afa | ||
|
|
8d97686b81 | ||
|
|
b16f6b9561 | ||
|
|
a546effa15 | ||
|
|
699f96ca0d | ||
|
|
5a0644d32b | ||
|
|
903f476f77 | ||
|
|
bf1ab03332 | ||
|
|
82cb1133e5 | ||
|
|
9b9c61a194 | ||
|
|
c768d05b5b | ||
|
|
fe018efc70 |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -29,8 +29,8 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew unlink python3
|
||||
# upgrade from python@3.11.2_1 to python@3.11.3 fails to overwrite those
|
||||
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.11 /usr/local/bin/idle3 /usr/local/bin/idle3.11 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.11 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.11 /usr/local/bin/python3.11-config
|
||||
# upgrade from python@3.12 to python@3.12.2 fails to overwrite those
|
||||
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.12 /usr/local/bin/idle3 /usr/local/bin/idle3.12 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.12 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.12 /usr/local/bin/python3.12-config
|
||||
brew install pkg-config ninja meson
|
||||
env:
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
|
||||
14
ChangeLog
14
ChangeLog
@@ -1,3 +1,14 @@
|
||||
libkiwix 13.1.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Properly translated error pages (@veloman-yunkan #1032)
|
||||
- Properly translated search result page (@veloman-yunkan #1046)
|
||||
- Default UI language is resolved in frontend (@veloman-yunkan #1044)
|
||||
- Better support of older Web browsers by polyfilling replaceAll() (@veloman-yunkan #1054)
|
||||
* New API to migrate bookmarks between books (@mgautierfr #1043)
|
||||
* Fixed compilation on Haiku OS (@Begasus #1048)
|
||||
|
||||
libkiwix 13.0.0
|
||||
===============
|
||||
|
||||
@@ -53,8 +64,6 @@ libkiwix 12.1.0
|
||||
* Remove libkiwix android publisher from the repository (@kelson42 #884)
|
||||
* Various fixes of meson and CI. (@mgautierfr @kelson42)
|
||||
|
||||
|
||||
|
||||
libkiwix 12.0.0
|
||||
===============
|
||||
|
||||
@@ -94,7 +103,6 @@ libkiwix 12.0.0
|
||||
* Fix documentation (@kelson42 #816)
|
||||
* Udpate translation (#787 #839 #847)
|
||||
|
||||
|
||||
libkiwix 11.0.0
|
||||
===============
|
||||
|
||||
|
||||
@@ -29,19 +29,33 @@ class xml_node;
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class Book;
|
||||
/**
|
||||
* A class to store information about a bookmark (an article in a book)
|
||||
*/
|
||||
class Bookmark
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create an empty bookmark.
|
||||
*
|
||||
* Bookmark must be populated with `set*` methods
|
||||
*/
|
||||
Bookmark();
|
||||
|
||||
/**
|
||||
* Create a bookmark given a Book, a path and a title.
|
||||
*/
|
||||
Bookmark(const Book& book, const std::string& path, const std::string& title);
|
||||
|
||||
~Bookmark();
|
||||
|
||||
void updateFromXml(const pugi::xml_node& node);
|
||||
|
||||
const std::string& getBookId() const { return m_bookId; }
|
||||
const std::string& getBookTitle() const { return m_bookTitle; }
|
||||
const std::string& getBookName() const { return m_bookName; }
|
||||
const std::string& getBookFlavour() const { return m_bookFlavour; }
|
||||
const std::string& getUrl() const { return m_url; }
|
||||
const std::string& getTitle() const { return m_title; }
|
||||
const std::string& getLanguage() const { return m_language; }
|
||||
@@ -49,6 +63,8 @@ class Bookmark
|
||||
|
||||
void setBookId(const std::string& bookId) { m_bookId = bookId; }
|
||||
void setBookTitle(const std::string& bookTitle) { m_bookTitle = bookTitle; }
|
||||
void setBookName(const std::string& bookName) { m_bookName = bookName; }
|
||||
void setBookFlavour(const std::string& bookFlavour) { m_bookFlavour = bookFlavour; }
|
||||
void setUrl(const std::string& url) { m_url = url; }
|
||||
void setTitle(const std::string& title) { m_title = title; }
|
||||
void setLanguage(const std::string& language) { m_language = language; }
|
||||
@@ -57,6 +73,8 @@ class Bookmark
|
||||
protected:
|
||||
std::string m_bookId;
|
||||
std::string m_bookTitle;
|
||||
std::string m_bookName;
|
||||
std::string m_bookFlavour;
|
||||
std::string m_url;
|
||||
std::string m_title;
|
||||
std::string m_language;
|
||||
|
||||
@@ -55,6 +55,22 @@ enum supportedListMode {
|
||||
NOVALID = 1 << 5
|
||||
};
|
||||
|
||||
enum MigrationMode {
|
||||
/** When migrating bookmarks, do not allow to migrate to an older book than the currently pointed one
|
||||
* (or date stored in the bookmark if book is invalid)
|
||||
*
|
||||
* If no newer books are found, no upgrade is made.
|
||||
*/
|
||||
UPGRADE_ONLY = 0,
|
||||
|
||||
/** Try hard to do a migration. This mostly does:
|
||||
* - Try to find a newer book.
|
||||
* - If book is invalid: find a best book, potentially older.
|
||||
* Older book will never be returned if current book is a valid one.
|
||||
*/
|
||||
ALLOW_DOWNGRADE = 1,
|
||||
};
|
||||
|
||||
class Filter {
|
||||
public: // types
|
||||
using Tags = std::vector<std::string>;
|
||||
@@ -71,6 +87,7 @@ class Filter {
|
||||
std::string _query;
|
||||
bool _queryIsPartial;
|
||||
std::string _name;
|
||||
std::string _flavour;
|
||||
|
||||
public: // functions
|
||||
Filter();
|
||||
@@ -130,6 +147,7 @@ class Filter {
|
||||
Filter& maxSize(size_t size);
|
||||
Filter& query(std::string query, bool partial=true);
|
||||
Filter& name(std::string name);
|
||||
Filter& flavour(std::string flavour);
|
||||
Filter& clearLang();
|
||||
Filter& clearCategory();
|
||||
|
||||
@@ -152,6 +170,9 @@ class Filter {
|
||||
bool hasCreator() const;
|
||||
const std::string& getCreator() const { return _creator; }
|
||||
|
||||
bool hasFlavour() const;
|
||||
const std::string& getFlavour() const { return _flavour; }
|
||||
|
||||
const Tags& getAcceptTags() const { return _acceptTags; }
|
||||
const Tags& getRejectTags() const { return _rejectTags; }
|
||||
|
||||
@@ -250,7 +271,7 @@ class Library: public std::enable_shared_from_this<Library>
|
||||
void addBookmark(const Bookmark& bookmark);
|
||||
|
||||
/**
|
||||
* Remove a bookmarkk
|
||||
* Remove a bookmark
|
||||
*
|
||||
* @param zimId The zimId of the bookmark.
|
||||
* @param url The url of the bookmark.
|
||||
@@ -258,6 +279,66 @@ class Library: public std::enable_shared_from_this<Library>
|
||||
*/
|
||||
bool removeBookmark(const std::string& zimId, const std::string& url);
|
||||
|
||||
/**
|
||||
* Migrate all invalid bookmarks.
|
||||
*
|
||||
* All invalid bookmarks (ie pointing to unknown books, no check is made on bookmark pointing to
|
||||
* invalid articles of valid book) will be migrated (if possible) to a better book.
|
||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
||||
*
|
||||
* @return A tuple<int, int>: <The number of bookmarks updated>, <Number of invalid bookmarks before migration was performed>.
|
||||
*/
|
||||
std::tuple<int, int> migrateBookmarks(MigrationMode migrationMode = ALLOW_DOWNGRADE);
|
||||
|
||||
/**
|
||||
* Migrate all bookmarks associated to a specific book.
|
||||
*
|
||||
* All bookmarks associated to `sourceBookId` book will be migrated to a better book.
|
||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
||||
*
|
||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
||||
* @param migrationMode how we will find the best book.
|
||||
* @return The number of bookmarks updated.
|
||||
*/
|
||||
int migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode = UPGRADE_ONLY);
|
||||
|
||||
/**
|
||||
* Migrate bookmarks
|
||||
*
|
||||
* Migrate all bookmarks pointing to `source` to `destination`.
|
||||
*
|
||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
||||
* @param targetBookId the destination bookId to migrate the bookmarks to.
|
||||
* @return The number of bookmarks updated.
|
||||
*/
|
||||
int migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId);
|
||||
|
||||
/**
|
||||
* Get the best available bookId for a bookmark.
|
||||
*
|
||||
* Given a bookmark, return the best available bookId.
|
||||
* "best available bookId" is determined using heuristitcs based on book name, flavour and date.
|
||||
*
|
||||
* @param bookmark The bookmark to search the bookId for.
|
||||
* @param migrationMode The migration mode to use.
|
||||
* @return A bookId. Potentially empty string if no suitable book found.
|
||||
*/
|
||||
std::string getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const;
|
||||
|
||||
/**
|
||||
* Get the best bookId for a combination of book's name, flavour and date.
|
||||
*
|
||||
* Given a bookName (mandatory), try to find the best book.
|
||||
* If preferedFlavour is given, will try to find a book with the same flavour. If not found, return a book with a different flavour.
|
||||
* If minDate is given, return a book newer than minDate. If not found, return a empty bookId.
|
||||
*
|
||||
* @param bookName The name of the book
|
||||
* @param preferedFlavour The prefered flavour.
|
||||
* @param minDate the minimal book date acceptable. Must be a string in the format "YYYY-MM-DD".
|
||||
* @return A bookId corresponding to the query, or empty string if not found.
|
||||
*/
|
||||
std::string getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour="", const std::string& minDate="") const;
|
||||
|
||||
// XXX: This is a non-thread-safe operation
|
||||
const Book& getBookById(const std::string& id) const;
|
||||
// XXX: This is a non-thread-safe operation
|
||||
@@ -403,12 +484,13 @@ private: // functions
|
||||
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
|
||||
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
||||
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
||||
std::string getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const;
|
||||
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
|
||||
void updateBookDB(const Book& book);
|
||||
void dropCache(const std::string& bookId);
|
||||
|
||||
private: //data
|
||||
mutable std::mutex m_mutex;
|
||||
mutable std::recursive_mutex m_mutex;
|
||||
Library::Revision m_revision;
|
||||
std::map<std::string, Entry> m_books;
|
||||
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '13.0.0',
|
||||
version : '13.1.0',
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "bookmark.h"
|
||||
#include "book.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
@@ -28,6 +29,17 @@ Bookmark::Bookmark()
|
||||
{
|
||||
}
|
||||
|
||||
Bookmark::Bookmark(const Book& book, const std::string& path, const std::string& title):
|
||||
m_bookId(book.getId()),
|
||||
m_bookTitle(book.getTitle()),
|
||||
m_bookName(book.getName()),
|
||||
m_bookFlavour(book.getFlavour()),
|
||||
m_url(path),
|
||||
m_title(title),
|
||||
m_language(book.getCommaSeparatedLanguages()),
|
||||
m_date(book.getDate())
|
||||
{}
|
||||
|
||||
/* Destructor */
|
||||
Bookmark::~Bookmark()
|
||||
{
|
||||
@@ -38,6 +50,8 @@ void Bookmark::updateFromXml(const pugi::xml_node& node)
|
||||
auto bookNode = node.child("book");
|
||||
m_bookId = bookNode.child("id").child_value();
|
||||
m_bookTitle = bookNode.child("title").child_value();
|
||||
m_bookName = bookNode.child("name").child_value();
|
||||
m_bookFlavour = bookNode.child("flavour").child_value();
|
||||
m_language = bookNode.child("language").child_value();
|
||||
m_date = bookNode.child("date").child_value();
|
||||
m_title = node.child("title").child_value();
|
||||
|
||||
211
src/library.cpp
211
src/library.cpp
@@ -110,7 +110,7 @@ Library::~Library() = default;
|
||||
|
||||
bool Library::addBook(const Book& book)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
++m_revision;
|
||||
/* Try to find it */
|
||||
updateBookDB(book);
|
||||
@@ -141,13 +141,13 @@ bool Library::addBook(const Book& book)
|
||||
|
||||
void Library::addBookmark(const Bookmark& bookmark)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
m_bookmarks.push_back(bookmark);
|
||||
}
|
||||
|
||||
bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) {
|
||||
if (it->getBookId() == zimId && it->getUrl() == url) {
|
||||
m_bookmarks.erase(it);
|
||||
@@ -157,6 +157,159 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<int, int> Library::migrateBookmarks(MigrationMode migrationMode) {
|
||||
std::set<std::string> sourceBooks;
|
||||
int invalidBookmarks = 0;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
if (m_books.find(bookmark.getBookId()) == m_books.end()) {
|
||||
invalidBookmarks += 1;
|
||||
sourceBooks.insert(bookmark.getBookId());
|
||||
}
|
||||
}
|
||||
}
|
||||
int changed = 0;
|
||||
for(auto& sourceBook:sourceBooks) {
|
||||
changed += migrateBookmarks(sourceBook, migrationMode);
|
||||
}
|
||||
return std::make_tuple(changed, invalidBookmarks);
|
||||
}
|
||||
|
||||
std::string Library::getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const {
|
||||
// This function try to get the best book for a bookmark from a book collection.
|
||||
// It assumes that all books in the collection are "acceptable".
|
||||
// (this definiton is not clear but for now it is book's name is equal to bookmark's bookName)
|
||||
//
|
||||
// The algorithm first sort the colletion by "flavour equality" and date.
|
||||
// "flavour equality" is if book's flavour is same that bookmark's flavour (let's say "flavourA" here)
|
||||
// So we have the sorted collection:
|
||||
// - flavourA, date 5
|
||||
// - flavourA, date 4
|
||||
// - flavourB, date 6
|
||||
// - flavourC, date 5
|
||||
// - flavourB, date 3
|
||||
//
|
||||
// Then, depending of migrationMode:
|
||||
// - If ALLOW_DOWNGRADE => take the first one
|
||||
// - If UPGRADE_ONLY => loop on books until we find a book newer than bookmark.
|
||||
// So if bookmark date is 5 => flavourB, date 6
|
||||
// if bookmark date is 4 => flavourA, date 5
|
||||
// if bookmark date is 7 => No book
|
||||
|
||||
if (books.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
sort(books, DATE, false);
|
||||
stable_sort(books.begin(), books.end(), [&](const std::string& bookId1, const std::string& bookId2) {
|
||||
const auto& book1 = getBookById(bookId1);
|
||||
const auto& book2 = getBookById(bookId2);
|
||||
bool same_flavour1 = book1.getFlavour() == bookmark.getBookFlavour();
|
||||
bool same_flavour2 = book2.getFlavour() == bookmark.getBookFlavour();
|
||||
// return True if bookId1 is before bookId2, ie if same_flavour1 and not same_flavour2
|
||||
return same_flavour1 > same_flavour2;
|
||||
});
|
||||
|
||||
if (migrationMode == ALLOW_DOWNGRADE) {
|
||||
return books[0];
|
||||
} else {
|
||||
for (const auto& bookId: books) {
|
||||
const auto& book = getBookById(bookId);
|
||||
if (book.getDate() >= bookmark.getDate()) {
|
||||
return bookId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string remove_quote(std::string input) {
|
||||
std::replace(input.begin(), input.end(), '"', ' ');
|
||||
return input;
|
||||
}
|
||||
|
||||
std::string Library::getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour, const std::string& minDate) const {
|
||||
// Let's reuse our algorithm based on bookmark.
|
||||
MigrationMode migrationMode = UPGRADE_ONLY;
|
||||
auto bookmark = Bookmark();
|
||||
bookmark.setBookName(bookName);
|
||||
bookmark.setBookFlavour(preferedFlavour);
|
||||
|
||||
if (minDate.empty()) {
|
||||
migrationMode = ALLOW_DOWNGRADE;
|
||||
} else {
|
||||
bookmark.setDate(minDate);
|
||||
}
|
||||
|
||||
return getBestTargetBookId(bookmark, migrationMode);
|
||||
}
|
||||
|
||||
std::string Library::getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
// Search for a existing book with the same name
|
||||
auto book_filter = Filter();
|
||||
if (!bookmark.getBookName().empty()) {
|
||||
book_filter.name(bookmark.getBookName());
|
||||
} else {
|
||||
// We don't have a name stored (older bookmarks)
|
||||
// Fallback on title (All bookmarks should have one, but let's be safe against wrongly filled bookmark)
|
||||
if (bookmark.getBookTitle().empty()) {
|
||||
// No bookName nor bookTitle, no way to find target book.
|
||||
return "";
|
||||
}
|
||||
book_filter.query("title:\"" + remove_quote(bookmark.getBookTitle()) + "\"");
|
||||
}
|
||||
auto targetBooks = filter(book_filter);
|
||||
auto bestBook = getBestFromBookCollection(targetBooks, bookmark, migrationMode);
|
||||
if (bestBook.empty()) {
|
||||
try {
|
||||
getBookById(bookmark.getBookId());
|
||||
return bookmark.getBookId();
|
||||
} catch (std::out_of_range&) {}
|
||||
}
|
||||
return bestBook;
|
||||
}
|
||||
|
||||
int Library::migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
|
||||
Bookmark firstBookmarkToChange;
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
if (bookmark.getBookId() == sourceBookId) {
|
||||
firstBookmarkToChange = bookmark;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstBookmarkToChange.getBookId().empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string betterBook = getBestTargetBookId(firstBookmarkToChange, migrationMode);
|
||||
|
||||
if (betterBook.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return migrateBookmarks(sourceBookId, betterBook);
|
||||
}
|
||||
|
||||
int Library::migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId) {
|
||||
if (sourceBookId == targetBookId) {
|
||||
return 0;
|
||||
}
|
||||
int changed = 0;
|
||||
for (auto& bookmark:m_bookmarks) {
|
||||
if (bookmark.getBookId() == sourceBookId) {
|
||||
bookmark.setBookId(targetBookId);
|
||||
changed +=1;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
void Library::dropCache(const std::string& id)
|
||||
{
|
||||
@@ -166,7 +319,7 @@ void Library::dropCache(const std::string& id)
|
||||
|
||||
bool Library::removeBookById(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
m_bookDB->delete_document("Q" + id);
|
||||
dropCache(id);
|
||||
// We do not change the cache size here
|
||||
@@ -184,7 +337,7 @@ bool Library::removeBookById(const std::string& id)
|
||||
|
||||
Library::Revision Library::getRevision() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
return m_revision;
|
||||
}
|
||||
|
||||
@@ -192,7 +345,7 @@ uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
|
||||
{
|
||||
BookIdCollection booksToRemove;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
for ( const auto& entry : m_books) {
|
||||
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
|
||||
booksToRemove.push_back(entry.first);
|
||||
@@ -217,7 +370,7 @@ const Book& Library::getBookById(const std::string& id) const
|
||||
|
||||
Book Library::getBookByIdThreadSafe(const std::string& id) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
return getBookById(id);
|
||||
}
|
||||
|
||||
@@ -275,7 +428,7 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
|
||||
unsigned int Library::getBookCount(const bool localBooks,
|
||||
const bool remoteBooks) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
return getBookCount_not_protected(localBooks, remoteBooks);
|
||||
}
|
||||
|
||||
@@ -288,7 +441,7 @@ bool Library::writeToFile(const std::string& path) const
|
||||
dumper.setBaseDir(baseDir);
|
||||
std::string xml;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
xml = dumper.dumpLibXMLContent(allBookIds);
|
||||
};
|
||||
return writeTextFile(path, xml);
|
||||
@@ -304,7 +457,7 @@ bool Library::writeBookmarksToFile(const std::string& path) const
|
||||
|
||||
Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
AttributeCounts propValueCounts;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -336,7 +489,7 @@ std::vector<std::string> Library::getBooksLanguages() const
|
||||
|
||||
Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
AttributeCounts langsWithCounts;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -352,7 +505,7 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
||||
|
||||
std::vector<std::string> Library::getBooksCategories() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::set<std::string> categories;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -383,7 +536,7 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
||||
}
|
||||
std::vector<kiwix::Bookmark> validBookmarks;
|
||||
auto booksId = getBooksIds();
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
|
||||
validBookmarks.push_back(bookmark);
|
||||
@@ -394,7 +547,7 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
||||
|
||||
Library::BookIdCollection Library::getBooksIds() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
BookIdCollection bookIds;
|
||||
|
||||
for (auto& pair: m_books) {
|
||||
@@ -437,6 +590,7 @@ void Library::updateBookDB(const Book& book)
|
||||
indexer.index_text(normalizeText(book.getCreator()), 1, "A");
|
||||
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
|
||||
doc.add_term("XN"+normalizeText(book.getName()));
|
||||
indexer.index_text(normalizeText(book.getFlavour()), 1, "XF");
|
||||
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
|
||||
|
||||
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
|
||||
@@ -477,6 +631,7 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
|
||||
queryParser.add_prefix("title", "S");
|
||||
queryParser.add_prefix("description", "XD");
|
||||
queryParser.add_prefix("name", "XN");
|
||||
queryParser.add_prefix("flavour", "XF");
|
||||
queryParser.add_prefix("category", "XC");
|
||||
queryParser.add_prefix("lang", "L");
|
||||
queryParser.add_prefix("publisher", "XP");
|
||||
@@ -503,6 +658,11 @@ Xapian::Query nameQuery(const std::string& name)
|
||||
return Xapian::Query("XN" + normalizeText(name));
|
||||
}
|
||||
|
||||
Xapian::Query flavourQuery(const std::string& name)
|
||||
{
|
||||
return Xapian::Query("XF" + normalizeText(name));
|
||||
}
|
||||
|
||||
Xapian::Query multipleParamQuery(const std::string& commaSeparatedList, const std::string& prefix)
|
||||
{
|
||||
Xapian::Query q;
|
||||
@@ -570,6 +730,9 @@ Xapian::Query buildXapianQuery(const Filter& filter)
|
||||
if ( filter.hasName() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, nameQuery(filter.getName()));
|
||||
}
|
||||
if ( filter.hasFlavour() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, flavourQuery(filter.getFlavour()));
|
||||
}
|
||||
if ( filter.hasCategory() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, categoryQuery(filter.getCategory()));
|
||||
}
|
||||
@@ -600,7 +763,7 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
|
||||
|
||||
BookIdCollection bookIds;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
Xapian::Enquire enquire(*m_bookDB);
|
||||
enquire.set_query(query);
|
||||
const auto results = enquire.get_mset(0, m_books.size());
|
||||
@@ -615,7 +778,7 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
|
||||
{
|
||||
BookIdCollection result;
|
||||
const auto preliminaryResult = filterViaBookDB(filter);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
for(auto id : preliminaryResult) {
|
||||
if(filter.accept(m_books.at(id))) {
|
||||
result.push_back(id);
|
||||
@@ -689,7 +852,7 @@ void Library::sort(BookIdCollection& bookIds, supportedListSortBy sort, bool asc
|
||||
// NOTE: for the entire duration of the sort. Will need to obtain (under a
|
||||
// NOTE: lock) the required atributes from the books once, and then the
|
||||
// NOTE: sorting will run on a copy of data without locking.
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
switch(sort) {
|
||||
case TITLE:
|
||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
|
||||
@@ -735,6 +898,7 @@ enum filterTypes {
|
||||
QUERY = FLAG(12),
|
||||
NAME = FLAG(13),
|
||||
CATEGORY = FLAG(14),
|
||||
FLAVOUR = FLAG(15),
|
||||
};
|
||||
|
||||
Filter& Filter::local(bool accept)
|
||||
@@ -836,6 +1000,13 @@ Filter& Filter::name(std::string name)
|
||||
activeFilters |= NAME;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::flavour(std::string flavour)
|
||||
{
|
||||
_flavour = flavour;
|
||||
activeFilters |= FLAVOUR;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::clearLang()
|
||||
{
|
||||
@@ -881,6 +1052,12 @@ bool Filter::hasCreator() const
|
||||
return ACTIVE(_CREATOR);
|
||||
}
|
||||
|
||||
bool Filter::hasFlavour() const
|
||||
{
|
||||
return ACTIVE(FLAVOUR);
|
||||
}
|
||||
|
||||
|
||||
bool Filter::accept(const Book& book) const
|
||||
{
|
||||
auto local = !book.getPath().empty();
|
||||
|
||||
@@ -97,11 +97,15 @@ void LibXMLDumper::handleBookmark(Bookmark bookmark, pugi::xml_node root_node) {
|
||||
auto book = library->getBookByIdThreadSafe(bookmark.getBookId());
|
||||
ADD_TEXT_ENTRY(book_node, "id", book.getId());
|
||||
ADD_TEXT_ENTRY(book_node, "title", book.getTitle());
|
||||
ADD_TEXT_ENTRY(book_node, "name", book.getName());
|
||||
ADD_TEXT_ENTRY(book_node, "flavour", book.getFlavour());
|
||||
ADD_TEXT_ENTRY(book_node, "language", book.getCommaSeparatedLanguages());
|
||||
ADD_TEXT_ENTRY(book_node, "date", book.getDate());
|
||||
} catch (...) {
|
||||
ADD_TEXT_ENTRY(book_node, "id", bookmark.getBookId());
|
||||
ADD_TEXT_ENTRY(book_node, "title", bookmark.getBookTitle());
|
||||
ADD_TEXT_ENTRY(book_node, "name", bookmark.getBookName());
|
||||
ADD_TEXT_ENTRY(book_node, "flavour", bookmark.getBookFlavour());
|
||||
ADD_TEXT_ENTRY(book_node, "language", bookmark.getLanguage());
|
||||
ADD_TEXT_ENTRY(book_node, "date", bookmark.getDate());
|
||||
}
|
||||
@@ -135,7 +139,7 @@ std::string LibXMLDumper::dumpLibXMLBookmark()
|
||||
pugi::xml_node bookmarksNode = doc.append_child("bookmarks");
|
||||
|
||||
if (library) {
|
||||
for (auto& bookmark: library->getBookmarks()) {
|
||||
for (auto& bookmark: library->getBookmarks(false)) {
|
||||
handleBookmark(bookmark, bookmarksNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
skin/i18n/ar.json
|
||||
skin/i18n/bn.json
|
||||
skin/i18n/br.json
|
||||
skin/i18n/cs.json
|
||||
skin/i18n/dag.json
|
||||
skin/i18n/de.json
|
||||
skin/i18n/dga.json
|
||||
skin/i18n/el.json
|
||||
@@ -8,10 +10,12 @@ skin/i18n/en.json
|
||||
skin/i18n/es.json
|
||||
skin/i18n/fi.json
|
||||
skin/i18n/fr.json
|
||||
skin/i18n/ha.json
|
||||
skin/i18n/he.json
|
||||
skin/i18n/hi.json
|
||||
skin/i18n/hy.json
|
||||
skin/i18n/ia.json
|
||||
skin/i18n/ig.json
|
||||
skin/i18n/it.json
|
||||
skin/i18n/ja.json
|
||||
skin/i18n/ko.json
|
||||
|
||||
@@ -17,6 +17,7 @@ skin/fonts/Poppins.ttf
|
||||
skin/fonts/Roboto.ttf
|
||||
skin/search_results.css
|
||||
skin/blank.html
|
||||
skin/polyfills.js
|
||||
skin/viewer.js
|
||||
skin/i18n.js
|
||||
skin/languages.js
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"404-page-heading": "পাওয়া যায়নি",
|
||||
"500-page-title": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"500-page-heading": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"search-result-book-info": "{{BOOK_TITLE}} থেকে",
|
||||
"word-count": "{{COUNT}}টি শব্দ",
|
||||
"library-button-text": "স্বাগত পাতায় চলুন",
|
||||
"home-button-text": "'{{BOOK_TITLE}}'-এর প্রধান পাতায় চলুন",
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' অনুসন্ধান করুন",
|
||||
|
||||
42
static/skin/i18n/br.json
Normal file
42
static/skin/i18n/br.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Adriendelucca",
|
||||
"Y-M D"
|
||||
]
|
||||
},
|
||||
"name": "brezhoneg",
|
||||
"suggest-full-text-search": "E lec'h emañ \"{{{SEARCH_TERMS}}}\"...",
|
||||
"no-such-book": "N’eus ket eus al levr-mañ: {{BOOK_NAME}}",
|
||||
"no-book-found": "N’eus levr ebet a glot gant an dezverkoù-se",
|
||||
"url-not-found": "N’eo ket bet kavet an URL \"{{url}}\" goulennet war ar servijer-mañ.",
|
||||
"random-article-failure": "Chaous! N’hon eus ket gellet dibab ur pennad dre ziouer evidoc’h :(",
|
||||
"400-page-title": "Reked amwiriek",
|
||||
"400-page-heading": "Reked amwiriek",
|
||||
"404-page-heading": "N'eo ket bet kavet",
|
||||
"500-page-title": "Fazi diabarzh ar servijer",
|
||||
"500-page-heading": "Fazi diabarzh ar servijer",
|
||||
"search-results-page-title": "Klask: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Disoc’hoù <b>{{START}}-{{END}}</b> diwar <b>{{COUNT}}</b> evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Disoc’h ebet kavet evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "diouzh {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} a c’herioù",
|
||||
"library-button-text": "Mont d’ar bajenn degemer",
|
||||
"home-button-text": "Mont da bajenn degemer \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "Mont d’ur bajenn dre zegouezh",
|
||||
"searchbox-tooltip": "Klask '{{BOOK_TITLE}}'",
|
||||
"powered-by-kiwix-html": "Lusket gant <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Klask",
|
||||
"book-filtering-all-categories": "An holl rummadoù",
|
||||
"book-filtering-all-languages": "An holl yezhoù",
|
||||
"count-of-matching-books": "{{COUNT}} levr",
|
||||
"download": "Pellgargañ",
|
||||
"direct-download-link-text": "Eeun",
|
||||
"filter-by-tag": "Silañ gant an dikedenn \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Paouez da silañ gant an dikedenn \"{{TAG}}\"",
|
||||
"welcome-to-kiwix-server": "Degemer mat er servijer Kiwix",
|
||||
"download-links-heading": "Liammoù pellgargañ evit <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Pellgargañ al levr",
|
||||
"preview-book": "Rakwelet",
|
||||
"unknown-error": "Fazi dianav"
|
||||
}
|
||||
55
static/skin/i18n/dag.json
Normal file
55
static/skin/i18n/dag.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Kalakpagh",
|
||||
"Ruky Wunpini"
|
||||
]
|
||||
},
|
||||
"name": "Silimiinsili",
|
||||
"suggest-full-text-search": "Gbubi la '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Lala buku kani:{{BOOK_NAME}}",
|
||||
"too-many-books": "Buku nima pam ka bɛ daa suhi ({{NB_BOOKS}}) din ni ka tariga nyɛ {{LIMIT}}",
|
||||
"no-book-found": "Buku kani lu zahim a ni piigi yaɣa shɛli",
|
||||
"url-not-found": "URL \"{{url}}\" shɛli bɛ ni daa suhi daa kani n-ti tum tumda ŋɔ.",
|
||||
"suggest-search": "Niŋmi lahabali pali vihigu zaŋ n-ti <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Oops! Zaɣisiya ni di gahim piigi lahabali :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} nyɛla din suhibu bi niŋ viɛnyɛla zaŋ n-ti lahabali kahili.",
|
||||
"invalid-request": "URL \"{{{url}}}\" shɛli bɛ ni daa suhi ŋɔ nyɛla din bi suhi viɛnyɛla.",
|
||||
"no-value-for-arg": "Dariza shɛli bi ti zaŋ n-ti nangban'kpeeni {{ARGUMENT}}",
|
||||
"no-query": "Yɛlshɛli bi yiina",
|
||||
"raw-entry-not-found": "Ku tooi nya {{DATATYPE}} kpɛbu {{ENTRY}}",
|
||||
"400-page-title": "Suhigu din bi niŋ viɛnyɛla",
|
||||
"400-page-heading": "Suhigu din bi niŋ viɛnyɛla",
|
||||
"404-page-title": "Lahabali kani",
|
||||
"404-page-heading": "Kani",
|
||||
"500-page-title": "Puuni tum tumda chiriŋ",
|
||||
"500-page-heading": "Puuni tum tumda chiriŋ",
|
||||
"500-page-text": "Puuni tum tumda chiriŋ niŋya. Ti niŋ yolitem zaŋ jɛndi li :/",
|
||||
"fulltext-search-unavailable": "Lahabali pali vihigu kani",
|
||||
"search-results-page-title": "Vihima:{{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Chaɣili nima <b>{{START}}-{{END}}</b> of <b>{{COUNT}}</b> for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Chaɣili daa kani zaŋ n-ti\n <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "yina {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} bachi nima",
|
||||
"library-button-text": "Cham solɔɣu",
|
||||
"home-button-text": "Cham yaɣili maŋmaŋ zaŋ n-ti\n'{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Cham gahim piigi yaɣili",
|
||||
"searchbox-tooltip": "Vihima '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Buku nima ayi bee gari balli koŋkoba nyɛ din yɛn be vihigu ŋɔ ni ka di ni tooi chɛ ka di laasabu wali.",
|
||||
"welcome-page-overzealous-filter": "Labisibu kani. A ni yu ni a\n<a href=\"{{URL}}\">reset filter</a>?",
|
||||
"powered-by-kiwix-html": "Din niŋ li nyɛ <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Vihima",
|
||||
"book-filtering-all-categories": "Pubu zaa",
|
||||
"book-filtering-all-languages": "Bala zaa",
|
||||
"count-of-matching-books": "{{COUNT}} Buku(nima)",
|
||||
"download": "Yihibu",
|
||||
"direct-download-link-text": "Tibi",
|
||||
"direct-download-alt-text": "Tibi deebu",
|
||||
"hash-download-link-text": "Sha256 hash",
|
||||
"hash-download-alt-text": "Deebu daliŋ",
|
||||
"welcome-to-kiwix-server": "Maraba Kiwix tum tumda",
|
||||
"download-links-heading": "Deemi soli zaŋ n-ti <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Yaa mi buku",
|
||||
"preview-book": "Labi lihi",
|
||||
"unknown-error": "Chiriŋ din bi tooi baŋ"
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"authors": [
|
||||
"IMayBeABitShy",
|
||||
"Lucas Werkmeister",
|
||||
"Rofiatmustapha12",
|
||||
"ThisCarthing"
|
||||
]
|
||||
},
|
||||
@@ -15,6 +16,7 @@
|
||||
"suggest-search": "Führe eine Volltextsuche nach <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> durch",
|
||||
"random-article-failure": "Hoppla! Konnte keinen zufälligen Artikel auswählen :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ist keine gültige Anfrage für unverarbeiteten Inhalt",
|
||||
"invalid-request": "Die angeforderte URL „{{{url}}}“ ist keine gültige Anfrage.",
|
||||
"no-value-for-arg": "Kein Wert für den Parameter {{ARGUMENT}} gegeben",
|
||||
"no-query": "Keine Suchanfrage gegeben.",
|
||||
"raw-entry-not-found": "Eintrag {{ENTRY}} des Typs {{DATATYPE}} konnte nicht gefunden werden.",
|
||||
@@ -24,8 +26,14 @@
|
||||
"404-page-heading": "Nicht gefunden",
|
||||
"500-page-title": "Interner Server-Fehler",
|
||||
"500-page-heading": "Interner Server-Fehler",
|
||||
"500-page-text": "Es ist ein interner Serverfehler aufgetreten. Das tut uns leid :/",
|
||||
"fulltext-search-unavailable": "Die Volltestsuche steht nicht zur Verfügung.",
|
||||
"no-search-results": "Die Volltextsuche ist für diesen Inhalt nicht verfügbar.",
|
||||
"search-results-page-title": "Suche: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Ergebnisse <b>{{START}}-{{END}}</b> von <b>{{COUNT}}</b> für <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"empty-search-results-page-header": "Für <b>„{{{SEARCH_PATTERN}}}“</b> wurden keine Ergebnisse gefunden.",
|
||||
"search-result-book-info": "von {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} Wörter",
|
||||
"library-button-text": "Zur Willkommensseite gehen",
|
||||
"home-button-text": "Zur Hauptseite von '{{BOOK_TITLE}}' gehen",
|
||||
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
|
||||
@@ -53,5 +61,6 @@
|
||||
"welcome-to-kiwix-server": "Wilkommen beim Kiwix Server",
|
||||
"download-links-heading": "Download Links für <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Buch herunterladen",
|
||||
"preview-book": "Vorschau"
|
||||
"preview-book": "Vorschau",
|
||||
"unknown-error": "Unbekannter Fehler"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Adriendelucca",
|
||||
"Gomoko",
|
||||
"Melimeli",
|
||||
"Stephane",
|
||||
"Thibaut120094",
|
||||
"Urhixidur",
|
||||
"Verdy p",
|
||||
"Vikoula5",
|
||||
"Wladek92"
|
||||
@@ -31,6 +34,11 @@
|
||||
"500-page-text": "Une erreur de serveur interne s'est produite. Nous en sommes désolés :/",
|
||||
"fulltext-search-unavailable": "Recherche en texte intégral non disponible",
|
||||
"no-search-results": "Le moteur de recherche en texte intégral n’est pas disponible pour ce contenu.",
|
||||
"search-results-page-title": "Rechercher : {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Résultats <b>{{START}}-{{END}}</b> sur<b> {{COUNT}}</b> pour <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Aucun résultat n’a été trouvé pour <b>« {{{SEARCH_PATTERN}}} »</b>",
|
||||
"search-result-book-info": "à partir de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} mots",
|
||||
"library-button-text": "Aller à la page de bienvenue",
|
||||
"home-button-text": "Aller à la page principale de « {{BOOK_TITLE}} »",
|
||||
"random-page-button-text": "Aller à une page sélectionnée aléatoirement",
|
||||
|
||||
67
static/skin/i18n/ha.json
Normal file
67
static/skin/i18n/ha.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Abelidokoo",
|
||||
"El-hussain14",
|
||||
"Rofiatmustapha12",
|
||||
"Smshika",
|
||||
"Yusuf Sa'adu"
|
||||
]
|
||||
},
|
||||
"name": "Turanci",
|
||||
"suggest-full-text-search": "dauke da ''{{{SEARCH_TERMS}}}''...",
|
||||
"no-such-book": "Babu irin wannan littafin: {{BOOK_NAME}}",
|
||||
"too-many-books": "An nemi littattafai da yawa ({{NB_BOOKS}}) inda iyaka shine {{LIMIT}}",
|
||||
"no-book-found": "Babu wani littafi da ya dace da ma'aunin zaɓi",
|
||||
"url-not-found": "Ba a sami URL ɗin da ake nema \"{{url}}\" akan wannan sabar ba.",
|
||||
"suggest-search": "Yi cikakken bincike na rubutu don <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Kash! An kasa ɗaukar labarin bazuwar :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ba ingantaccen buƙatun ɗanyen abun ciki bane.",
|
||||
"invalid-request": "URL ɗin da ake nema \"{{{url}}}\" ba buƙatu mai inganci bane.",
|
||||
"no-value-for-arg": "Babu ƙima da aka bayar don hujja {{ARGUMENT}}",
|
||||
"no-query": "Ba a bayar da tambaya ba.",
|
||||
"raw-entry-not-found": "Ba a iya samun shigarwar {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "nema mara inganci",
|
||||
"400-page-heading": "nema mara inganci",
|
||||
"404-page-title": "Ba a samo abun ciki ba",
|
||||
"404-page-heading": "Ba a Samu ba",
|
||||
"500-page-title": "Kuskuren na Cikin Saba",
|
||||
"500-page-heading": "Kuskuren na Cikin Saba",
|
||||
"500-page-text": "An sami kuskuren uwar garken ciki. Munyi nadama akan hakan :/",
|
||||
"fulltext-search-unavailable": "Babu binciken cikakken rubutu",
|
||||
"no-search-results": "Babu injin binciken cikakken rubutu don wannan abun ciki.",
|
||||
"search-results-page-title": "Bincika: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Sakamako <b>{{START}}-{{END}}</b> na <b>{{COUNT}}</b> na <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Ba a sami sakamakon <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "daga {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} kalmomi",
|
||||
"library-button-text": "Je zuwa shafin maraba",
|
||||
"home-button-text": "Jeka babban shafin '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Je zuwa shafin da aka zaɓa ba da gangan ba",
|
||||
"searchbox-tooltip": "Bincika '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Littattafai biyu ko fiye a cikin harsuna daban-daban za su shiga cikin bincike, wanda zai iya haifar da sakamako mai ruɗani.",
|
||||
"welcome-page-overzealous-filter": "Babu sakamako. Kuna so a <a href=\"{{URL}}\">sake saita tace</a>?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> ne ke ƙarfafa shi",
|
||||
"search": "Nema",
|
||||
"book-filtering-all-categories": "Dukkanin nau'o'in",
|
||||
"book-filtering-all-languages": "Duka harsuna",
|
||||
"count-of-matching-books": "{{COUNT}} littafi(s)",
|
||||
"download": "Sauke",
|
||||
"direct-download-link-text": "Kai tsaye",
|
||||
"direct-download-alt-text": "saukewa kai tsaye",
|
||||
"hash-download-link-text": "Sha256 hash",
|
||||
"hash-download-alt-text": "sauke hash",
|
||||
"magnet-link-text": "Magnet link",
|
||||
"magnet-alt-text": "Magnet ɗin saukewa",
|
||||
"torrent-download-link-text": "Torrent fayil",
|
||||
"torrent-download-alt-text": "download torrent",
|
||||
"library-opds-feed-all-entries": "Ciyarwar OPDS Library - Duk shigarwar",
|
||||
"filter-by-tag": "Tace da alamar \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Dakatar da tacewa ta hanyar \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": " OPDS ciyar wa OPDS- entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Barka da zowa manhajar Kiwix",
|
||||
"download-links-heading": "Bring out ways through which people can join hands together .{{BOOK_TITLE}}",
|
||||
"download-links-title": "Sauke littafin",
|
||||
"preview-book": "Dubawa",
|
||||
"unknown-error": "Kuskuren da ba a sani ba"
|
||||
}
|
||||
@@ -27,6 +27,11 @@
|
||||
"500-page-text": "אירעה שגיאת שרת פנימית. אנחנו מצטערים על זה :/",
|
||||
"fulltext-search-unavailable": "חיפוש בטקסט מלא אינו זמין",
|
||||
"no-search-results": "מנוע החיפוש בטקסט מלא אינו זמין עבור התוכן הזה.",
|
||||
"search-results-page-title": "חיפוש: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "תוצאות <b>{{START}} עד {{END}}</b> מתוך <b>{{COUNT}}</b> עבור <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "לא נמצאו תוצאות עבור <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "מתוך {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} מילים",
|
||||
"library-button-text": "מעבר לדף הבית \"ברוך בואך\"",
|
||||
"home-button-text": "מעבר לדף הראשי של \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "מעבר לדף שנבחר אקראית",
|
||||
|
||||
65
static/skin/i18n/ig.json
Normal file
65
static/skin/i18n/ig.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Accuratecy051",
|
||||
"Ngostary2k",
|
||||
"Oby Ezeilo"
|
||||
]
|
||||
},
|
||||
"name": "Bekee",
|
||||
"suggest-full-text-search": "nwere {{{SEARCH_TERMS}}}'",
|
||||
"no-such-book": "Enweghị akwụkwọ dị otú a: {{BOOK_NAME}}",
|
||||
"too-many-books": "Arịrịọ ọtụtụ akwụkwọ ({{NB_BOOKS}}) ebe oke bụ {{LIMIT}}",
|
||||
"no-book-found": "Ọ nweghị akwụkwọ dabara na nhọpụta nhọrọ",
|
||||
"url-not-found": "Ahụghị URL a rịọrọ \"{{url}}\" na nkesa a.",
|
||||
"suggest-search": "Mee ọchụchọ ederede zuru oke maka <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ee! Ịhọrọ akụkọ enweghị usoro :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} abụghị arịrịọ ziri ezi maka ọdịnaya raw.",
|
||||
"invalid-request": "Arịrịọ gbasara URL \"{{{url}}}\" e zighi ezi.",
|
||||
"no-value-for-arg": "Ọ nweghị uru enyere maka arụmụka {{ARGUMENT}}",
|
||||
"no-query": "Ọnweghị ajụjụ enyere.",
|
||||
"raw-entry-not-found": "Enweghị ike ịchọta ntinye {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Arịrịọ na-ezighi ezi",
|
||||
"400-page-heading": "Arịrịọ na-ezighi ezi",
|
||||
"404-page-title": "Ahụghị ọdịnaya",
|
||||
"404-page-heading": "Ahụghị",
|
||||
"500-page-title": "Mperi Sava Ime",
|
||||
"500-page-heading": "Mperi Sava Ime",
|
||||
"500-page-text": "Enwere mperi ihe nkesa dị n'ime. Ọ dị anyị nwute na nke ahụ :/",
|
||||
"fulltext-search-unavailable": "Ọchịchọ ederede zuru ezu adịghị",
|
||||
"no-search-results": "Igwe nchọta ederede zuru oke adịghị maka ọdịnaya a.",
|
||||
"search-results-page-title": "Chọọ: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Rịzọlt ga <b>{{START}}-{{END}}</b> nke <b>{{COUNT}}</b> maka <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Ọnweghị rịzọlt ahụrụ maka <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "sitere na {{BOOK_TITLE}}",
|
||||
"word-count": "Okwu {{COUNT}}",
|
||||
"library-button-text": "Gaa na ibe nnabata",
|
||||
"home-button-text": "Gaa na isi ibe nke '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Gaa na ibe ahọpụtara enweghị usoro",
|
||||
"searchbox-tooltip": "Chọọ '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Akwụkwọ abụọ ma ọ bụ karịa n'asụsụ dị iche iche ga-esonye na nchọ, nke nwere ike ibute nsonaazụ mgbagwoju anya.",
|
||||
"welcome-page-overzealous-filter": "Enweghị nsonaazụ. Ọ ga-amasị gị <a href=\"{{URL}}\">ịtọgharịa nzacha</a> ?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> kwadoro ya",
|
||||
"search": "Chọọ",
|
||||
"book-filtering-all-categories": "Nkeji niile",
|
||||
"book-filtering-all-languages": "Asụsụ niile",
|
||||
"count-of-matching-books": "akwụkwọ {{COUNT}}",
|
||||
"download": "Budata",
|
||||
"direct-download-link-text": "Gosi",
|
||||
"direct-download-alt-text": "nbudata ozugbo",
|
||||
"hash-download-link-text": "Sha256 hash",
|
||||
"hash-download-alt-text": "budata hash",
|
||||
"magnet-link-text": "Njikọ magnet",
|
||||
"magnet-alt-text": "ibudata magnet",
|
||||
"torrent-download-link-text": " faịlụ nke Torrent",
|
||||
"torrent-download-alt-text": "Budata torrent",
|
||||
"library-opds-feed-all-entries": "Ọbá akwụkwọ OPDS Feed - Ihe niile",
|
||||
"filter-by-tag": "Wepụta site na mkpado \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Kwụsị nzacha site na mkpado \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Ọbá akwụkwọ OPDS nri - ndenye dabara na {{#LANG}}\nAsụsụ: {{LANG}} {{/LANG}}{{#CATEGORY}}\n Kategori: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\n Ajụjụ: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Nabata na Kiwix Server",
|
||||
"download-links-heading": "Budata njikọ maka <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Budata akwụkwọ",
|
||||
"preview-book": "Ziwe nkirimaàtụ̀",
|
||||
"unknown-error": "amaghị m njehie"
|
||||
}
|
||||
@@ -24,6 +24,11 @@
|
||||
"500-page-title": "Errore interno del server",
|
||||
"500-page-heading": "Errore interno del server",
|
||||
"500-page-text": "Si è verificato un errore interno del server. Ci dispiace :/",
|
||||
"search-results-page-title": "Cerca: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Risultati <b>{{START}}-{{END}}</b> di <b>{{COUNT}}</b> per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Non è stato trovato alcun risultato per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "da {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} parole",
|
||||
"library-button-text": "Vai alla pagina di benvenuto",
|
||||
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
"500-page-text": "Настана внатрешна грешка во опслужувачот. Жал ни е :/",
|
||||
"fulltext-search-unavailable": "Целотекстното пребарување е недостапно",
|
||||
"no-search-results": "Погонот за целотекстно пребарување не е достапен за оваа содржина.",
|
||||
"search-results-page-title": "Пребарување: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Исход <b>{{START}}-{{END}}</b> од <b>{{COUNT}}</b> за <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"empty-search-results-page-header": "Не најдов ништо за <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"search-result-book-info": "од {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} зборови",
|
||||
"library-button-text": "Оди на воведната страница",
|
||||
"home-button-text": "Оди на главната страница на „{{BOOK_TITLE}}“",
|
||||
"random-page-button-text": "Оди на случајно избрана страница",
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
"home-button-text": "Przejdź do głównej strony '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Przejdź do losowo wybranej strony",
|
||||
"searchbox-tooltip": "Szukaj '{{BOOK_TITLE}}'",
|
||||
"search": "Szukaj",
|
||||
"book-filtering-all-categories": "Wszystkie Kategorie",
|
||||
"book-filtering-all-languages": "Wszystkie języki",
|
||||
"download": "Pobierz",
|
||||
"direct-download-link-text": "Bezpośrednio",
|
||||
"direct-download-alt-text": "bezpośrednie pobieranie",
|
||||
"torrent-download-link-text": "Plik torrent",
|
||||
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
|
||||
"download-links-title": "Pobierz książkę",
|
||||
"preview-book": "Podgląd"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"Okras",
|
||||
"Pacha Tchernof",
|
||||
"Razno0",
|
||||
"Rofiatmustapha12",
|
||||
"Smavrina"
|
||||
]
|
||||
},
|
||||
@@ -18,6 +19,7 @@
|
||||
"suggest-search": "Выполните полнотекстовый поиск для <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ой! Не удалось выбрать случайную статью :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} не является допустимым запросом необработанного контента.",
|
||||
"invalid-request": "Запрошенный URL-адрес «{{{url}}}» не является допустимым запросом.",
|
||||
"no-value-for-arg": "Не указано значение для аргумента {{ARGUMENT}}",
|
||||
"no-query": "Не предоставлен запрос.",
|
||||
"raw-entry-not-found": "Не удаётся найти запись {{ENTRY}} типа {{DATATYPE}}",
|
||||
@@ -27,30 +29,41 @@
|
||||
"404-page-heading": "Не найдено",
|
||||
"500-page-title": "Внутренняя ошибка сервера",
|
||||
"500-page-heading": "Внутренняя ошибка сервера",
|
||||
"500-page-text": "Произошла внутренняя ошибка сервера. Мы сожалеем об этом :/",
|
||||
"fulltext-search-unavailable": "Полнотекстовый поиск недоступен",
|
||||
"no-search-results": "Полнотекстовая поисковая система недоступна для этого содержания.",
|
||||
"search-results-page-title": "Поиск: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Результаты <b>{{START}}-{{END}}</b> из <b>{{COUNT}}</b> для <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "По запросу <b>\"{{{SEARCH_PATTERN}}}\"</b> результатов не найдено.",
|
||||
"search-result-book-info": "из {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} слов",
|
||||
"library-button-text": "Перейти на страницу-приветствие",
|
||||
"home-button-text": "Перейти на главную страницу '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Перейти на случайно выбранную страницу",
|
||||
"searchbox-tooltip": "Искать '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "В поиске будут участвовать две или более книг на разных языках, что может привести к запутанным результатам.",
|
||||
"welcome-page-overzealous-filter": "Безрезультатно. Хотите <a href=\"{{URL}}\">сбросить фильтр</a> ?",
|
||||
"powered-by-kiwix-html": "При поддержке <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Найти",
|
||||
"book-filtering-all-categories": "Все категории",
|
||||
"book-filtering-all-languages": "Все языки",
|
||||
"count-of-matching-books": "{{COUNT}} книг(и)",
|
||||
"download": "Скачать",
|
||||
"direct-download-link-text": "Прямой",
|
||||
"direct-download-alt-text": "прямая загрузка",
|
||||
"hash-download-link-text": "Хэш Sha256",
|
||||
"hash-download-alt-text": "скачать хэш",
|
||||
"magnet-link-text": "Магнитная ссылка",
|
||||
"magnet-alt-text": "скачать магнит",
|
||||
"torrent-download-link-text": "Торрент-файл",
|
||||
"torrent-download-alt-text": "скачать торрент",
|
||||
"library-opds-feed-all-entries": "Канал библиотеки OPDS – все записи",
|
||||
"filter-by-tag": "Фильтровать по тегу \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Прекратить фильтрацию по тегу \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Канал OPDS библиотеки – записи, соответствующие {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nЗапрос: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Добро пожаловать на сервер Kiwix",
|
||||
"download-links-heading": "Ссылки для скачивания <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Скачать книгу",
|
||||
"preview-book": "Предпросмотр"
|
||||
"preview-book": "Предпросмотр",
|
||||
"unknown-error": "Неизвестная ошибка"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Eleassar",
|
||||
"Kelson"
|
||||
"Kelson",
|
||||
"Rofiatmustapha12"
|
||||
]
|
||||
},
|
||||
"name": "slovenščina",
|
||||
@@ -27,6 +28,11 @@
|
||||
"500-page-text": "Prišlo je do notranje napake strežnika. Žal nam je za to. :/",
|
||||
"fulltext-search-unavailable": "Iskanje po celotnem besedilu ni na voljo",
|
||||
"no-search-results": "Iskalnik po celotnem besedilu za to vsebino ni na voljo.",
|
||||
"search-results-page-title": "Iskanje: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Zadetki <b>{{START}}–{{END}}</b> od <b>{{COUNT}}</b> za »<b>{{{SEARCH_PATTERN}}}</b>«",
|
||||
"empty-search-results-page-header": "Ni zadetkov za »<b>{{{SEARCH_PATTERN}}}</b>«",
|
||||
"search-result-book-info": "iz {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} besed",
|
||||
"library-button-text": "Pojdite na pozdravno stran",
|
||||
"home-button-text": "Pojdite na glavno stran »{{BOOK_TITLE}}«",
|
||||
"random-page-button-text": "Pojdite na naključno izbrano stran",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Jopparn",
|
||||
"Rofiatmustapha12",
|
||||
"Sabelöga",
|
||||
"WikiPhoenix"
|
||||
]
|
||||
@@ -28,6 +29,11 @@
|
||||
"500-page-text": "Ett internt serverfel uppstod. Vi ber om ursäkt för det :/",
|
||||
"fulltext-search-unavailable": "Fulltextsökning är inte tillgänglig",
|
||||
"no-search-results": "Sökmaskinen för fulltext är inte tillgänglig för detta innehåll.",
|
||||
"search-results-page-title": "Sök: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultat <b>{{START}}-{{END}}</b> av <b>{{COUNT}}</b> för <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Inga resultat hittades för <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "från {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} ord",
|
||||
"library-button-text": "Gå till hemsidan",
|
||||
"home-button-text": "Gå till huvudsidan för \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "Gå till en slumpmässigt utvald sida",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Hedda"
|
||||
"Hedda",
|
||||
"Rofiatmustapha12"
|
||||
]
|
||||
},
|
||||
"name": "Türkçe",
|
||||
@@ -13,6 +14,7 @@
|
||||
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> için tam metin araması yapın",
|
||||
"random-article-failure": "Hata! Rastgele bir madde seçilemedi :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}}, ham içerik için geçerli bir istek değil.",
|
||||
"invalid-request": "İstenen \"{{{url}}}\" URL'si geçerli bir istek değil.",
|
||||
"no-value-for-arg": "{{ARGUMENT}} bağımsız değişkeni için değer sağlanmadı",
|
||||
"no-query": "Sorgu sağlanmadı.",
|
||||
"raw-entry-not-found": "{{DATATYPE}} {{ENTRY}} girişi bulunamadı",
|
||||
@@ -22,10 +24,41 @@
|
||||
"404-page-heading": "Bulunamadı",
|
||||
"500-page-title": "İç Sunucu Hatası",
|
||||
"500-page-heading": "İç Sunucu Hatası",
|
||||
"500-page-text": "Dahili bir sunucu hatası oluştu. Bunun için üzgünüz :/",
|
||||
"fulltext-search-unavailable": "Tam metin araması kullanılamıyor",
|
||||
"no-search-results": "Tam metin arama motoru bu içerik için kullanılamaz.",
|
||||
"search-results-page-title": "Arama: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b> için <b>{{COUNT}}</b> sonuçtan <b>{{START}}-{{END}}</b> arası sonuçlar",
|
||||
"empty-search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b> için sonuç bulunamadı",
|
||||
"search-result-book-info": "{{BOOK_TITLE}} adlı kitaptan",
|
||||
"word-count": "{{COUNT}} kelime",
|
||||
"library-button-text": "Karşılama sayfasına git",
|
||||
"home-button-text": "'{{BOOK_TITLE}}' anasayfasına gidin",
|
||||
"random-page-button-text": "Rastgele seçilen bir sayfaya git",
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' ara"
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' ara",
|
||||
"confusion-of-tongues": "Aramaya farklı dillerde iki veya daha fazla kitap katılacak ve bu da kafa karıştırıcı sonuçlara yol açabilecektir.",
|
||||
"welcome-page-overzealous-filter": "Sonuç yok. <a href=\"{{URL}}\">Filtreyi sıfırlamak</a> ister misiniz?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> tarafından desteklenmektedir",
|
||||
"search": "Ara",
|
||||
"book-filtering-all-categories": "Tüm kategoriler",
|
||||
"book-filtering-all-languages": "Tüm diller",
|
||||
"count-of-matching-books": "{{COUNT}} kitap",
|
||||
"download": "İndir",
|
||||
"direct-download-link-text": "Doğrudan",
|
||||
"direct-download-alt-text": "direkt indirme",
|
||||
"hash-download-link-text": "Sha256 haşesi",
|
||||
"hash-download-alt-text": "csv indir",
|
||||
"magnet-link-text": "Mıknatıs bağlantısı",
|
||||
"magnet-alt-text": "mıknatısı indir",
|
||||
"torrent-download-link-text": "Hedef dosya",
|
||||
"torrent-download-alt-text": "torrenti indir",
|
||||
"library-opds-feed-all-entries": "Kütüphane OPDS Akışı - Tüm girişler",
|
||||
"filter-by-tag": "\"{{TAG}}\" etiketine göre filtrele",
|
||||
"stop-filtering-by-tag": "\"{{TAG}}\" etiketine göre filtrelemeyi durdur",
|
||||
"library-opds-feed-parameterised": "Kütüphane OPDS Özet Akışı - {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} ile eşleşen girişler {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Kiwix Sunucusuna Hoş Geldiniz",
|
||||
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> için indirme bağlantıları",
|
||||
"download-links-title": "Kitapları indir",
|
||||
"preview-book": "Önizleme",
|
||||
"unknown-error": "Bilinmeyen hata"
|
||||
}
|
||||
|
||||
@@ -2,24 +2,63 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"GuoPC",
|
||||
"IceButBin",
|
||||
"StarrySky",
|
||||
"Sunai",
|
||||
"XtexChooser"
|
||||
]
|
||||
},
|
||||
"name": "英语",
|
||||
"suggest-full-text-search": "正在查找「{{{SEARCH_TERMS}}}」…",
|
||||
"no-such-book": "没有名为“{{BOOK_NAME}}”的图书",
|
||||
"too-many-books": "请求的图书过多 ({{NB_BOOKS}}),上限为 {{LIMIT}}",
|
||||
"no-book-found": "没有符合搜索要求的图书",
|
||||
"url-not-found": "在此服务器上找不到请求的 URL:{{url}}",
|
||||
"suggest-search": "对<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>进行全文搜索",
|
||||
"random-article-failure": "抱歉!随机条目失败了 (〒﹏〒)",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} 对原请求无效。",
|
||||
"invalid-request": "请求的URL无效:{{{url}}}",
|
||||
"no-value-for-arg": "参数{{ARGUMENT}}无值",
|
||||
"no-query": "未提供查询。",
|
||||
"raw-entry-not-found": "找不到 {{DATATYPE}} 条目 {{ENTRY}}",
|
||||
"400-page-title": "无效请求",
|
||||
"400-page-heading": "无效请求",
|
||||
"404-page-title": "未找到内容",
|
||||
"404-page-heading": "未找到",
|
||||
"500-page-title": "内部服务器错误",
|
||||
"500-page-heading": "内部服务器错误",
|
||||
"500-page-text": "内部服务器出现错误。真的十分抱歉 (;ŏ﹏ŏ)",
|
||||
"fulltext-search-unavailable": "全文搜索不可用",
|
||||
"no-search-results": "全文搜索引擎不适用于该内容。",
|
||||
"search-results-page-title": "搜索:{{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>“{{{SEARCH_PATTERN}}}”</b>的<b>第 {{START}}-{{END}}</b>个结果(共<b>{{COUNT}}</b>个)",
|
||||
"empty-search-results-page-header": "未找到<b>“{{{SEARCH_PATTERN}}}”</b>的结果",
|
||||
"search-result-book-info": "来自{{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} 个字",
|
||||
"library-button-text": "前往欢迎页面",
|
||||
"home-button-text": "转到“{{BOOK_TITLE}}”的主页",
|
||||
"random-page-button-text": "前往随机选择的页面",
|
||||
"searchbox-tooltip": "搜索“{{BOOK_TITLE}}”",
|
||||
"confusion-of-tongues": "两本或多本不同语言的图书将同时被搜索,这可能会导致搜索结果混乱。",
|
||||
"welcome-page-overzealous-filter": "没有结果。您想<a href=\"{{URL}}\">重置过滤器</a>吗?",
|
||||
"powered-by-kiwix-html": "由<a href=\"https://kiwix.org\">Kiwix</a>提供技术支持",
|
||||
"search": "搜索",
|
||||
"book-filtering-all-categories": "所有分类",
|
||||
"book-filtering-all-languages": "所有语言",
|
||||
"count-of-matching-books": "{{COUNT}} 本书",
|
||||
"download": "下载",
|
||||
"direct-download-link-text": "直接",
|
||||
"direct-download-alt-text": "直接下載",
|
||||
"hash-download-link-text": "Sha256 哈希值",
|
||||
"hash-download-alt-text": "下载哈希值",
|
||||
"magnet-link-text": "磁力链接",
|
||||
"magnet-alt-text": "下载磁力链接",
|
||||
"torrent-download-link-text": "种子文件",
|
||||
"torrent-download-alt-text": "下载种子文件",
|
||||
"library-opds-feed-all-entries": "图书馆 OPDS Feed - 所有条目",
|
||||
"filter-by-tag": "按标签“{{TAG}}”过滤",
|
||||
"stop-filtering-by-tag": "停止按标签“{{TAG}}”过滤",
|
||||
"library-opds-feed-parameterised": "图书馆 OPDS Feed - 匹配的项目 {{#LANG}}\n语言:{{LANG}} {{/LANG}}{{#CATEGORY}}\n分类:{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n标签:{{TAG}} {{/TAG}}{{#Q}}\n查询:{{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "欢迎来到 Kiwix 服务器",
|
||||
"preview-book": "预览"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"500-page-text": "內部伺服器發生錯誤。對此我們深感抱歉:/",
|
||||
"fulltext-search-unavailable": "全文搜尋無效",
|
||||
"no-search-results": "全文搜尋引擎不適用此內容。",
|
||||
"search-results-page-title": "搜尋:{{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>「{{{SEARCH_PATTERN}}}」</b>的<b>第{{START}}-{{END}}筆</b>結果(共<b>{{COUNT}}</b>筆)",
|
||||
"empty-search-results-page-header": "未找到<b>「{{{SEARCH_PATTERN}}}」</b>的結果",
|
||||
"search-result-book-info": "來自{{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}}個字",
|
||||
"library-button-text": "前往歡迎首頁",
|
||||
"home-button-text": "前往「{{BOOK_TITLE}}」的首頁",
|
||||
"random-page-button-text": "前往隨機選取頁面",
|
||||
|
||||
@@ -7,17 +7,27 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 12
|
||||
"translation_count": 14
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
"self_name": "brezhoneg",
|
||||
"translation_count": 35
|
||||
},
|
||||
{
|
||||
"iso_code": "cs",
|
||||
"self_name": "Čeština",
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "dag",
|
||||
"self_name": "Silimiinsili",
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"iso_code": "de",
|
||||
"self_name": "Deutsch",
|
||||
"translation_count": 49
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "en",
|
||||
@@ -37,12 +47,17 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
"self_name": "Turanci",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
@@ -59,10 +74,15 @@ const uiLanguages = [
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 49
|
||||
},
|
||||
{
|
||||
"iso_code": "ig",
|
||||
"self_name": "Bekee",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 29
|
||||
"translation_count": 34
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -87,7 +107,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
@@ -112,12 +132,12 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "pl",
|
||||
"self_name": "Polski",
|
||||
"translation_count": 24
|
||||
"translation_count": 31
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
"self_name": "русский",
|
||||
"translation_count": 45
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "sc",
|
||||
@@ -137,7 +157,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sl",
|
||||
"self_name": "slovenščina",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "sq",
|
||||
@@ -147,7 +167,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sv",
|
||||
"self_name": "Svenska",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "te",
|
||||
@@ -157,16 +177,16 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "tr",
|
||||
"self_name": "Türkçe",
|
||||
"translation_count": 25
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hans",
|
||||
"self_name": "英语",
|
||||
"translation_count": 16
|
||||
"translation_count": 54
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hant",
|
||||
"self_name": "繁體中文",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
}
|
||||
]
|
||||
21
static/skin/polyfills.js
Normal file
21
static/skin/polyfills.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// A few browsers do not support the use of String.prototype.replaceAll method.
|
||||
// Hence we define it once we verify that it isn't supported. For documentation
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function (pattern, replacement) {
|
||||
// verify parameter: It must either be a string or a RegExp with a global flag.
|
||||
if (typeof pattern[Symbol.replace] === 'function') {
|
||||
// the pattern is a RegExp check for the presence of g flag.
|
||||
if (pattern.global) {
|
||||
return this.replace(pattern, replacement);
|
||||
} else {
|
||||
throw new TypeError('Global flag for regular expressions')
|
||||
}
|
||||
}
|
||||
// the pattern is not a RegExp, hence it must be a string.
|
||||
if (typeof pattern !== 'string') {
|
||||
throw new TypeError('pattern must either be a string or a RegExp with a global (g) flag.')
|
||||
}
|
||||
return this.replace(new RegExp(pattern, 'g'), replacement);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-config" content="{{root}}/skin/favicon/browserconfig.xml?KIWIXCACHEID">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<script type="text/javascript" src="./skin/polyfills.js?KIWIXCACHEID"></script>
|
||||
<script type="text/javascript" src="./viewer_settings.js"></script>
|
||||
<script type="module" src="{{root}}/skin/i18n.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/languages.js?KIWIXCACHEID" defer></script>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<link type="text/css" href="./skin/kiwix.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./skin/polyfills.js?KIWIXCACHEID"></script>
|
||||
<script type="text/javascript" src="./viewer_settings.js"></script>
|
||||
<script type="module" src="./skin/i18n.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?KIWIXCACHEID" defer></script>
|
||||
|
||||
493
test/library.cpp
493
test/library.cpp
@@ -20,7 +20,6 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include <string>
|
||||
|
||||
|
||||
const char * sampleOpdsStream = R"(
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:dc="http://purl.org/dc/terms/"
|
||||
@@ -28,12 +27,12 @@ const char * sampleOpdsStream = R"(
|
||||
<id>00000000-0000-0000-0000-000000000000</id>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie_novid_2018-10</name>
|
||||
<flavour>unforgettable</flavour>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>novid</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd</id>
|
||||
<icon>/meta?name=favicon&content=wikipedia_fr_tunisie_novid_2018-10</icon>
|
||||
<updated>2018-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>8 Oct 2018</dc:issued>
|
||||
<dc:issued>2018-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie</summary>
|
||||
<tags>wikipedia;novid;_ftindex</tags>
|
||||
@@ -49,9 +48,53 @@ const char * sampleOpdsStream = R"(
|
||||
<mediaCount>1100</mediaCount>
|
||||
<articleCount>172</articleCount>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>novid</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater</id>
|
||||
<updated>2019-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2019-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie. Updated in 2019</summary>
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>other_flavour</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd_flavour</id>
|
||||
<updated>2018-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2018-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie. With another flavour</summary>
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>other_flavour</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater_flavour</id>
|
||||
<updated>2019-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2019-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie. Updated in 2019, and other flavour</summary>
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Tania Louis</title>
|
||||
<id>urn:uuid:0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2</id>
|
||||
<name>biologie-tout-compris_fr_all</name>
|
||||
<flavour>full</flavour>
|
||||
<icon>/meta?name=favicon&content=biologie-tout-compris_fr_all_2018-06</icon>
|
||||
<updated>2018-06-23T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
@@ -67,6 +110,8 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Wikiquote</title>
|
||||
<id>urn:uuid:0ea1cde6-441d-6c58-f2c7-21c2838e659f</id>
|
||||
<name>wikiquote_fr_all</name>
|
||||
<flavour>full</flavour>
|
||||
<icon>/meta?name=favicon&content=wikiquote_fr_all_nopic_2019-06</icon>
|
||||
<updated>2019-06-05T00:00::00:Z</updated>
|
||||
<language>fra,ita</language>
|
||||
@@ -83,6 +128,8 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Géographie par Wikipédia</title>
|
||||
<id>urn:uuid:1123e574-6eef-6d54-28fc-13e4caeae474</id>
|
||||
<name>wikipedia_fr_geography</name>
|
||||
<flavour>full</flavour>
|
||||
<icon>/meta?name=favicon&content=wikipedia_fr_geography_nopic_2019-06</icon>
|
||||
<updated>2019-06-02T00:00::00:Z</updated>
|
||||
<summary>Une sélection d'articles de Wikipédia sur la géographie</summary>
|
||||
@@ -99,6 +146,8 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Mathématiques</title>
|
||||
<id>urn:uuid:14829621-c490-c376-0792-9de558b57efa</id>
|
||||
<name>wikipedia_fr_mathematics</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=wikipedia_fr_mathematics_nopic_2019-05</icon>
|
||||
<updated>2019-05-13T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
@@ -115,6 +164,8 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Granblue Fantasy Wiki</title>
|
||||
<id>urn:uuid:006cbd1b-16d8-b00d-a584-c1ae110a94ed</id>
|
||||
<name>grandbluefantasy_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=granbluefantasy_en_all_all_nopic_2018-10</icon>
|
||||
<updated>2018-10-14T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -130,6 +181,8 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Movies & TV Stack Exchange</title>
|
||||
<id>urn:uuid:00f37b00-f4da-0675-995a-770f9c72903e</id>
|
||||
<name>movies.stackexchange.com_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=movies.stackexchange.com_en_all_2019-02</icon>
|
||||
<updated>2019-02-03T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -143,8 +196,10 @@ const char * sampleOpdsStream = R"(
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=movies.stackexchange.com_en_all_2019-02" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>TED talks - Business</title>
|
||||
<title>TED"talks" - Business</title>
|
||||
<id>urn:uuid:0189d9be-2fd0-b4b6-7300-20fab0b5cdc8</id>
|
||||
<name>ted_en_business</name>
|
||||
<flavour>nodet</flavour>
|
||||
<icon>/meta?name=favicon&content=ted_en_business_2018-07</icon>
|
||||
<updated>2018-07-23T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -157,9 +212,28 @@ const char * sampleOpdsStream = R"(
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/ted/ted_en_business_2018-07.zim.meta4" length="8855827456" />
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=ted_en_business_2018-07" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Business talks about TED</title>
|
||||
<id>Dummy id </id>
|
||||
<name>speak_business</name>
|
||||
<flavour>nodet</flavour>
|
||||
<icon>/meta?name=favicon&content=ted_en_business_2018-07</icon>
|
||||
<updated>2018-08-23T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
<summary>Ideas worth spreading</summary>
|
||||
<tags></tags>
|
||||
<link type="text/html" href="/ted_en_business_2018-07" />
|
||||
<author>
|
||||
<name>TED</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/ted/ted_en_business_2018-07.zim.meta4" length="8855827456" />
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=ted_en_business_2018-07" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Mythology & Folklore Stack Exchange</title>
|
||||
<id>urn:uuid:028055ac-4acc-1d54-65e0-a96de45e1b22</id>
|
||||
<name>mythology.stackexchange.com_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=mythology.stackexchange.com_en_all_2019-02</icon>
|
||||
<updated>2019-02-03T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -175,6 +249,8 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Islam Stack Exchange</title>
|
||||
<id>urn:uuid:02e9c7ff-36fc-9c6e-6ac7-cd7085989029</id>
|
||||
<name>islam.stackexchange.com_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=islam.stackexchange.com_en_all_2019-01</icon>
|
||||
<updated>2019-01-31T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -229,6 +305,7 @@ const char sampleLibraryXML[] = R"(
|
||||
|
||||
#include "../include/library.h"
|
||||
#include "../include/manager.h"
|
||||
#include "../include/book.h"
|
||||
#include "../include/bookmark.h"
|
||||
|
||||
namespace
|
||||
@@ -242,17 +319,17 @@ TEST(LibraryOpdsImportTest, allInOne)
|
||||
kiwix::Manager manager(lib);
|
||||
manager.readOpds(sampleOpdsStream, "library-opds-import.unittests.dev");
|
||||
|
||||
EXPECT_EQ(10U, lib->getBookCount(true, true));
|
||||
EXPECT_EQ(14U, lib->getBookCount(true, true));
|
||||
|
||||
{
|
||||
const kiwix::Book& book1 = lib->getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
EXPECT_EQ(book1.getTitle(), "Encyclopédie de la Tunisie");
|
||||
EXPECT_EQ(book1.getName(), "wikipedia_fr_tunisie_novid_2018-10");
|
||||
EXPECT_EQ(book1.getFlavour(), "unforgettable");
|
||||
EXPECT_EQ(book1.getName(), "wikipedia_fr_tunisie");
|
||||
EXPECT_EQ(book1.getFlavour(), "novid");
|
||||
EXPECT_EQ(book1.getLanguages(), Langs{ "fra" });
|
||||
EXPECT_EQ(book1.getCommaSeparatedLanguages(), "fra");
|
||||
EXPECT_EQ(book1.getDate(), "8 Oct 2018");
|
||||
EXPECT_EQ(book1.getDate(), "2018-10-08");
|
||||
EXPECT_EQ(book1.getDescription(), "Le meilleur de Wikipédia sur la Tunisie");
|
||||
EXPECT_EQ(book1.getCreator(), "Wikipedia");
|
||||
EXPECT_EQ(book1.getPublisher(), "Wikipedia Publishing House");
|
||||
@@ -272,9 +349,9 @@ TEST(LibraryOpdsImportTest, allInOne)
|
||||
|
||||
{
|
||||
const kiwix::Book& book2 = lib->getBookById("0189d9be-2fd0-b4b6-7300-20fab0b5cdc8");
|
||||
EXPECT_EQ(book2.getTitle(), "TED talks - Business");
|
||||
EXPECT_EQ(book2.getName(), "");
|
||||
EXPECT_EQ(book2.getFlavour(), "");
|
||||
EXPECT_EQ(book2.getTitle(), "TED\"talks\" - Business");
|
||||
EXPECT_EQ(book2.getName(), "ted_en_business");
|
||||
EXPECT_EQ(book2.getFlavour(), "nodet");
|
||||
EXPECT_EQ(book2.getLanguages(), Langs{ "eng" });
|
||||
EXPECT_EQ(book2.getCommaSeparatedLanguages(), "eng");
|
||||
EXPECT_EQ(book2.getDate(), "2018-07-23");
|
||||
@@ -309,11 +386,18 @@ class LibraryTest : public ::testing::Test {
|
||||
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
|
||||
}
|
||||
|
||||
kiwix::Bookmark createBookmark(const std::string &id) {
|
||||
kiwix::Bookmark bookmark;
|
||||
bookmark.setBookId(id);
|
||||
return bookmark;
|
||||
};
|
||||
kiwix::Bookmark createBookmark(const std::string &id, const std::string& url="", const std::string& title="") {
|
||||
kiwix::Bookmark bookmark;
|
||||
bookmark.setBookId(id);
|
||||
bookmark.setUrl(url);
|
||||
bookmark.setTitle(title);
|
||||
return bookmark;
|
||||
};
|
||||
|
||||
kiwix::Bookmark createBookmark(const kiwix::Book& book, const std::string& url="", const std::string& title="") {
|
||||
kiwix::Bookmark bookmark(book, url, title);
|
||||
return bookmark;
|
||||
};
|
||||
|
||||
TitleCollection ids2Titles(const BookIdCollection& ids) {
|
||||
TitleCollection titles;
|
||||
@@ -327,14 +411,35 @@ class LibraryTest : public ::testing::Test {
|
||||
std::shared_ptr<kiwix::Library> lib;
|
||||
};
|
||||
|
||||
TEST_F(LibraryTest, createBookMark)
|
||||
{
|
||||
auto bookId = "0c45160e-f917-760a-9159-dfe3c53cdcdd";
|
||||
auto book = lib->getBookById(bookId);
|
||||
|
||||
auto bookmark = createBookmark(book, "/a/url", "A title");
|
||||
|
||||
EXPECT_EQ(bookmark.getUrl(), "/a/url");
|
||||
EXPECT_EQ(bookmark.getTitle(), "A title");
|
||||
EXPECT_EQ(bookmark.getBookId(), bookId);
|
||||
EXPECT_EQ(bookmark.getBookName(), book.getName());
|
||||
EXPECT_EQ(bookmark.getBookName(), "wikipedia_fr_tunisie");
|
||||
EXPECT_EQ(bookmark.getBookTitle(), book.getTitle());
|
||||
EXPECT_EQ(bookmark.getDate(), book.getDate());
|
||||
EXPECT_EQ(bookmark.getBookFlavour(), book.getFlavour());
|
||||
EXPECT_EQ(bookmark.getLanguage(), book.getCommaSeparatedLanguages());
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, getBookMarksTest)
|
||||
{
|
||||
auto bookId1 = lib->getBooksIds()[0];
|
||||
auto bookId2 = lib->getBooksIds()[1];
|
||||
auto bookId1 = "0c45160e-f917-760a-9159-dfe3c53cdcdd";
|
||||
auto bookId2 = "0189d9be-2fd0-b4b6-7300-20fab0b5cdc8";
|
||||
|
||||
lib->addBookmark(createBookmark(bookId1));
|
||||
lib->addBookmark(createBookmark("invalid-bookmark-id"));
|
||||
lib->addBookmark(createBookmark(bookId2));
|
||||
auto book1 = lib->getBookById(bookId1);
|
||||
auto book2 = lib->getBookById(bookId2);
|
||||
|
||||
lib->addBookmark(createBookmark(book1));
|
||||
lib->addBookmark(createBookmark("invalid-book-id"));
|
||||
lib->addBookmark(createBookmark(book2));
|
||||
auto onlyValidBookmarks = lib->getBookmarks();
|
||||
auto allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
@@ -342,13 +447,284 @@ TEST_F(LibraryTest, getBookMarksTest)
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-bookmark-id");
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, bookmarksSerializationTest)
|
||||
{
|
||||
auto bookId1 = lib->getBooksIds()[0];
|
||||
auto bookId2 = lib->getBooksIds()[1];
|
||||
|
||||
auto book1 = lib->getBookById(bookId1);
|
||||
auto book2 = lib->getBookById(bookId2);
|
||||
|
||||
// Create bookmarks using three different ways.
|
||||
lib->addBookmark(createBookmark(bookId1, "a/url", "Article title1"));
|
||||
lib->addBookmark(createBookmark("invalid-book-id", "another/url", "Unknown title"));
|
||||
lib->addBookmark(createBookmark(book2, "a/url/2", "Article title2"));
|
||||
|
||||
lib->writeBookmarksToFile("__test__bookmarks.xml");
|
||||
|
||||
// Build a new library
|
||||
auto new_lib = kiwix::Library::create();
|
||||
{
|
||||
kiwix::Manager manager(new_lib);
|
||||
manager.readOpds(sampleOpdsStream, "foo.urlHost");
|
||||
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
|
||||
manager.readBookmarkFile("__test__bookmarks.xml");
|
||||
}
|
||||
std::remove("__test__bookmarks.xml");
|
||||
|
||||
auto onlyValidBookmarks = new_lib->getBookmarks();
|
||||
auto allBookmarks = new_lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 2U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 3U);
|
||||
auto bookmark1 = allBookmarks[0];
|
||||
EXPECT_EQ(bookmark1.getBookId(), bookId1);
|
||||
EXPECT_EQ(bookmark1.getBookTitle(), book1.getTitle());
|
||||
EXPECT_EQ(bookmark1.getBookName(), book1.getName());
|
||||
EXPECT_EQ(bookmark1.getBookFlavour(), book1.getFlavour());
|
||||
EXPECT_EQ(bookmark1.getUrl(), "a/url");
|
||||
EXPECT_EQ(bookmark1.getTitle(), "Article title1");
|
||||
EXPECT_EQ(bookmark1.getLanguage(), book1.getCommaSeparatedLanguages());
|
||||
EXPECT_EQ(bookmark1.getDate(), book1.getDate());
|
||||
|
||||
auto bookmark2 = allBookmarks[1];
|
||||
EXPECT_EQ(bookmark2.getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(bookmark2.getBookTitle(), "");
|
||||
EXPECT_EQ(bookmark2.getBookName(), "");
|
||||
EXPECT_EQ(bookmark2.getBookFlavour(), "");
|
||||
EXPECT_EQ(bookmark2.getUrl(), "another/url");
|
||||
EXPECT_EQ(bookmark2.getTitle(), "Unknown title");
|
||||
EXPECT_EQ(bookmark2.getLanguage(), "");
|
||||
EXPECT_EQ(bookmark2.getDate(), "");
|
||||
|
||||
auto bookmark3 = allBookmarks[2];
|
||||
EXPECT_EQ(bookmark3.getBookId(), bookId2);
|
||||
EXPECT_EQ(bookmark3.getBookTitle(), book2.getTitle());
|
||||
EXPECT_EQ(bookmark3.getBookName(), book2.getName());
|
||||
EXPECT_EQ(bookmark3.getBookFlavour(), book2.getFlavour());
|
||||
EXPECT_EQ(bookmark3.getUrl(), "a/url/2");
|
||||
EXPECT_EQ(bookmark3.getTitle(), "Article title2");
|
||||
EXPECT_EQ(bookmark3.getLanguage(), book2.getCommaSeparatedLanguages());
|
||||
EXPECT_EQ(bookmark3.getDate(), book2.getDate());
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, MigrateBookmark)
|
||||
{
|
||||
std::string bookId1 = "0c45160e-f917-760a-9159-dfe3c53cdcdd";
|
||||
std::string bookId2 = "0189d9be-2fd0-b4b6-7300-20fab0b5cdc8";
|
||||
|
||||
auto book1 = lib->getBookById(bookId1);
|
||||
auto book1Flavour = lib->getBookById(bookId1+"_flavour");
|
||||
auto book2 = lib->getBookById(bookId2);
|
||||
|
||||
lib->addBookmark(createBookmark(book1));
|
||||
lib->addBookmark(createBookmark("invalid-book-id"));
|
||||
lib->addBookmark(createBookmark(book2));
|
||||
|
||||
auto wrongIdBookmark = createBookmark(book1);
|
||||
wrongIdBookmark.setBookId("wrong-book-id");
|
||||
lib->addBookmark(wrongIdBookmark);
|
||||
|
||||
auto wrongIdBookmarkNoName = createBookmark(book2);
|
||||
wrongIdBookmarkNoName.setBookId("wrong-book-id-noname");
|
||||
wrongIdBookmarkNoName.setBookName("");
|
||||
lib->addBookmark(wrongIdBookmarkNoName);
|
||||
|
||||
auto wrongIdFlavourBookmark = createBookmark(book1Flavour);
|
||||
wrongIdFlavourBookmark.setBookId("wrong-book-flavour-id");
|
||||
lib->addBookmark(wrongIdFlavourBookmark);
|
||||
|
||||
auto onlyValidBookmarks = lib->getBookmarks();
|
||||
auto allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 2U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), "wrong-book-id");
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), "wrong-book-id-noname");
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), "wrong-book-flavour-id");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks("no-existant-book"), 0);
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(), std::make_tuple(3, 4));
|
||||
|
||||
onlyValidBookmarks = lib->getBookmarks();
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 5U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[2].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(onlyValidBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[4].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(), std::make_tuple(0, 1));
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId1), 1);
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId1, bookId2), 0); // No more bookId1 bookmark
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId1+"_updated1yearlater", bookId2), 2);
|
||||
onlyValidBookmarks = lib->getBookmarks();
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 5U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[4].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks("invalid-book-id", bookId1), 1);
|
||||
|
||||
onlyValidBookmarks = lib->getBookmarks();
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 6U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdOlder)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
|
||||
auto validBookmark = createBookmark(book);
|
||||
lib->addBookmark(validBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::UPGRADE_ONLY), bookId+"_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::ALLOW_DOWNGRADE), bookId+"_updated1yearlater");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdNewer)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
EXPECT_EQ(book.getDate(), "2019-10-08");
|
||||
|
||||
auto validBookmark = createBookmark(book);
|
||||
// Make the bookmark more recent than any books in the library.
|
||||
// (But still pointing to existing book)
|
||||
validBookmark.setDate("2020-10-08");
|
||||
lib->addBookmark(validBookmark);
|
||||
|
||||
// The best book for the bookmark is bookId...
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::UPGRADE_ONLY), bookId);
|
||||
// but there is not migration to do as the bookmark already point to it.
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId, kiwix::UPGRADE_ONLY), 0);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::ALLOW_DOWNGRADE), bookId);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdInvalidOlder)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
|
||||
auto invalidBookmark = createBookmark(book);
|
||||
invalidBookmark.setBookId("invalid-book-id");
|
||||
lib->addBookmark(invalidBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::UPGRADE_ONLY), bookId+"_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::ALLOW_DOWNGRADE), bookId+"_updated1yearlater");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdInvalidNewer)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
EXPECT_EQ(book.getDate(), "2018-10-08");
|
||||
|
||||
auto invalidBookmark = createBookmark(book);
|
||||
invalidBookmark.setBookId("invalid-book-id");
|
||||
invalidBookmark.setDate("2020-10-08");
|
||||
lib->addBookmark(invalidBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::UPGRADE_ONLY), "");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::ALLOW_DOWNGRADE), bookId+"_updated1yearlater");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdFlavour)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd_flavour");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
EXPECT_EQ(book.getDate(), "2018-10-08");
|
||||
|
||||
auto invalidBookmark = createBookmark(book);
|
||||
invalidBookmark.setBookId("invalid-book-id");
|
||||
invalidBookmark.setDate("2020-10-08");
|
||||
lib->addBookmark(invalidBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::UPGRADE_ONLY), "");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::ALLOW_DOWNGRADE), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater_flavour");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdName)
|
||||
{
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie"), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie", "novid"), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie", "other_flavour"), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater_flavour");
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie", "other_flavour", "2020-12-12"), "");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, sanityCheck)
|
||||
{
|
||||
EXPECT_EQ(lib->getBookCount(true, true), 12U);
|
||||
EXPECT_EQ(lib->getBookCount(true, true), 16U);
|
||||
EXPECT_EQ(lib->getBooksLanguages(),
|
||||
std::vector<std::string>({"deu", "eng", "fra", "ita", "spa"})
|
||||
);
|
||||
@@ -400,6 +776,10 @@ TEST_F(LibraryTest, filterLocal)
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().local(false),
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -407,7 +787,7 @@ TEST_F(LibraryTest, filterLocal)
|
||||
"Mathématiques",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"TED talks - Business",
|
||||
"TED\"talks\" - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -416,6 +796,10 @@ TEST_F(LibraryTest, filterLocal)
|
||||
TEST_F(LibraryTest, filterRemote)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().remote(true),
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -424,7 +808,7 @@ TEST_F(LibraryTest, filterRemote)
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED talks - Business",
|
||||
"TED\"talks\" - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -437,21 +821,23 @@ TEST_F(LibraryTest, filterRemote)
|
||||
TEST_F(LibraryTest, filterByLanguage)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().lang("eng"),
|
||||
"Business talks about TED",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Islam Stack Exchange",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED talks - Business"
|
||||
"TED\"talks\" - Business"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("lang:eng"),
|
||||
"Business talks about TED",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Islam Stack Exchange",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED talks - Business"
|
||||
"TED\"talks\" - Business"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("eng"),
|
||||
@@ -459,6 +845,25 @@ TEST_F(LibraryTest, filterByLanguage)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterByFlavour)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().flavour("full"),
|
||||
"Géographie par Wikipédia",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("flavour:full"),
|
||||
"Géographie par Wikipédia",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("full"),
|
||||
/* no results */
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterByTags)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().acceptTags({"stackexchange"}),
|
||||
@@ -558,6 +963,9 @@ TEST_F(LibraryTest, filterByQuery)
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki"),
|
||||
"An example ZIM archive", // due to the "wikibooks" tag
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques", // due to the "wikipedia" tag
|
||||
@@ -576,6 +984,10 @@ TEST_F(LibraryTest, filteringByEmptyQueryReturnsAllEntries)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query(""),
|
||||
"An example ZIM archive",
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -584,7 +996,7 @@ TEST_F(LibraryTest, filteringByEmptyQueryReturnsAllEntries)
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED talks - Business",
|
||||
"TED\"talks\" - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -593,6 +1005,9 @@ TEST_F(LibraryTest, filteringByEmptyQueryReturnsAllEntries)
|
||||
TEST_F(LibraryTest, filterByCreator)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().creator("Wikipedia"),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques",
|
||||
@@ -634,6 +1049,9 @@ TEST_F(LibraryTest, filterByCreator)
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("creator:Wikipedia"),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques",
|
||||
@@ -741,6 +1159,9 @@ TEST_F(LibraryTest, filterByMaxSize)
|
||||
TEST_F(LibraryTest, filterByMultipleCriteria)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki").creator("Wikipedia"),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques", // due to the "wikipedia" tag
|
||||
@@ -748,11 +1169,17 @@ TEST_F(LibraryTest, filterByMultipleCriteria)
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki").creator("Wikipedia").maxSize(100000000UL),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Ray Charles"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki").creator("Wikipedia").maxSize(100000000UL).local(false),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie"
|
||||
);
|
||||
}
|
||||
@@ -810,6 +1237,10 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter(),
|
||||
"An example ZIM archive",
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -818,7 +1249,7 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED talks - Business",
|
||||
"TED\"talks\" - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -832,7 +1263,7 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
|
||||
const uint64_t rev2 = lib->getRevision();
|
||||
|
||||
EXPECT_EQ(9u, lib->removeBooksNotUpdatedSince(rev));
|
||||
EXPECT_EQ(13u, lib->removeBooksNotUpdatedSince(rev));
|
||||
|
||||
EXPECT_GT(lib->getRevision(), rev2);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ const ResourceCollection resources200Compressible{
|
||||
// TODO: implement cache management of i18n resources
|
||||
//{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n/test.json?cacheid=unknown" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/languages.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=9ccd43fd" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=5be77f5c" },
|
||||
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/search" },
|
||||
|
||||
@@ -146,6 +146,8 @@ const ResourceCollection resources200Uncompressible{
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/hash.png?cacheid=f836e872" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/magnet.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/magnet.png?cacheid=73b6bddf" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/polyfills.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/polyfills.js?cacheid=a0e0343d" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search-icon.svg" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/search-icon.svg?cacheid=b10ae7ed" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search_results.css" },
|
||||
@@ -285,8 +287,9 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
|
||||
<link rel="mask-icon" href="/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314">
|
||||
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
|
||||
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
|
||||
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=9ccd43fd" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=5be77f5c" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" defer></script>
|
||||
@@ -318,8 +321,9 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?cacheid=e014a885" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
|
||||
<script type="module" src="./skin/i18n.js?cacheid=071abc9a" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=9ccd43fd" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=5be77f5c" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=5fc4badf" defer></script>
|
||||
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
|
||||
@@ -1133,17 +1137,27 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 12
|
||||
"translation_count": 14
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
"self_name": "brezhoneg",
|
||||
"translation_count": 35
|
||||
},
|
||||
{
|
||||
"iso_code": "cs",
|
||||
"self_name": "Čeština",
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "dag",
|
||||
"self_name": "Silimiinsili",
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"iso_code": "de",
|
||||
"self_name": "Deutsch",
|
||||
"translation_count": 49
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "en",
|
||||
@@ -1163,12 +1177,17 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
"self_name": "Turanci",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
@@ -1185,10 +1204,15 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 49
|
||||
},
|
||||
{
|
||||
"iso_code": "ig",
|
||||
"self_name": "Bekee",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 29
|
||||
"translation_count": 34
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -1213,7 +1237,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
@@ -1238,12 +1262,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "pl",
|
||||
"self_name": "Polski",
|
||||
"translation_count": 24
|
||||
"translation_count": 31
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
"self_name": "русский",
|
||||
"translation_count": 45
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "sc",
|
||||
@@ -1263,7 +1287,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sl",
|
||||
"self_name": "slovenščina",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "sq",
|
||||
@@ -1273,7 +1297,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sv",
|
||||
"self_name": "Svenska",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "te",
|
||||
@@ -1283,17 +1307,17 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "tr",
|
||||
"self_name": "Türkçe",
|
||||
"translation_count": 25
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hans",
|
||||
"self_name": "英语",
|
||||
"translation_count": 16
|
||||
"translation_count": 54
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hant",
|
||||
"self_name": "繁體中文",
|
||||
"translation_count": 52
|
||||
"translation_count": 57
|
||||
}
|
||||
])EXPECTEDRESPONSE");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user