Compare commits

...

33 Commits

Author SHA1 Message Date
Kelson
ddde6db16f Merge pull request #1061 from kiwix/release-3.1.0
Release 3.1.0
2024-02-25 15:11:31 +01:00
Emmanuel Engelhart
50d1394a0a Add 13.1.0 Changelog 2024-02-25 15:11:13 +01:00
Emmanuel Engelhart
a6040b2ecd Bump-up version to 13.1.0 2024-02-25 15:11:13 +01:00
Kelson
4e755bc949 Merge pull request #1062 from kiwix/compilation_warnings
Fixed compilation warnings
2024-02-25 15:01:17 +01:00
Veloman Yunkan
cfab4c946a Fixed compilation warnings 2024-02-25 16:15:29 +04:00
Kelson
57a265f73c Merge pull request #1059 from kiwix/translatewiki 2024-02-22 19:23:34 +01:00
translatewiki.net
3f945813f2 Localisation updates from https://translatewiki.net. 2024-02-22 13:07:51 +01:00
Veloman Yunkan
86100b39ed Merge pull request #1047 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-02-20 14:40:42 +04:00
Matthieu Gautier
b2ae6d1fca Update i18n translation files. 2024-02-20 10:40:44 +01:00
translatewiki.net
e82b62c552 Localisation updates from https://translatewiki.net. 2024-02-19 13:07:49 +01:00
Kelson
5fba3f434e Merge pull request #1054 from kiwix/polyfilljs
Enter polyfills.js
2024-02-15 16:04:45 +01:00
Veloman Yunkan
3ac36e8ebd Enter polyfills.js
The `String.replaceAll` polyfill was borrowed (at 0% annual intereset
rate) from https://github.com/kiwix/kiwix-js/pull/1190/files.
2024-02-15 16:03:29 +01:00
Kelson
1babbc0e4a Merge pull request #1043 from kiwix/bookmarks_migrations
Migrate bookmarks between books
2024-02-15 16:01:46 +01:00
Matthieu Gautier
6b05eeb24b Add a small test on getBestTargetBookId and flavour. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
73b855ce6b Add a getBestTargetBookId directly taking bookName, flavour and date. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
eaca7010bc Fix definition of UPGRADE_ONLY and ALLOW_DOWNGRADE.
`MigrationMode` was kind of defined in the context of an internal mode
used by `migrateBookmark(...)`.
But now, with `getBestTargetBookId`, it is broken.

This commit fix that and the associated implementation.
Now `UPGRADE_ONLY` will make `getBestTargetBookId` return only newer books.
and `ALLOW_DOWNGRADE` will return older books only if current book is
invalid.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
6efdc43964 Correcly search for book's title with double quote (").
At indexation time, double quote are ignored, so a title as
`TED "talks" - Business` is indexed as `ted talks business`.

By removing the quotes, we ensure that our title "phrase" is not closed
too early and we correctly search for `ted PHRASE talks PHRASE business`
instead of `ted AND talks AND business`.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
7a0ab3a429 Update tests to check book's title with double quotes (")
On top of modifying the existing test, the commit also make
`MigrateBookmark` test fails as `migrateBookmarks` now migrates
from `wrong-book-id-noname` to `Dummy id`.

Fix will be provided in next commit.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
3e9d50fecb Make getBestTargetBookId public. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
f3a604380c Do not migrate bookmarks to an older book.
At least, it must be explicitly asked by the user.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
167e0dc4b3 Only migrate bookmarks to books with the same flavour.
If there is no book with the same flavour, but book with same name and
different flavour, we do the migration to the other book.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
14c9530afa [Test] Introduce variant books in sample library.
We will need them to test flavour/date bookmarks migration.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
8d97686b81 Introduce migrateBookmarks to move (invalid) bookmarks to new books. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
b16f6b9561 Allow to filter books by flavour. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
a546effa15 Allow bookmark to be created from a Book and url/title. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
699f96ca0d Add book's flavour in bookmark. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
5a0644d32b Also store book's name in bookmark. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
903f476f77 Test bookmarks serializations. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
bf1ab03332 [Test] Add missing flavour in books. 2024-02-15 14:52:57 +01:00
Matthieu Gautier
82cb1133e5 [Test] Add missing name in sample library.xml 2024-02-15 14:52:57 +01:00
Matthieu Gautier
9b9c61a194 Use a recursive_mutex instead of a mutex.
This allow us to internally call thread_safe function from already
locked context.
2024-02-15 14:52:57 +01:00
Matthieu Gautier
c768d05b5b Merge pull request #1056 from kiwix/fix_macos_build
[CI] Fix macos python installation.
2024-02-15 14:52:19 +01:00
Matthieu Gautier
fe018efc70 Update to new macos' python 3.12
Brew update its receipe about python and now use python 3.12 instead of
python 3.11.
2024-02-15 14:16:29 +01:00
33 changed files with 1269 additions and 91 deletions

View File

@@ -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

View File

@@ -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
===============

View File

@@ -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;

View File

@@ -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>>;

View File

@@ -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'])

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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": "Neus ket eus al levr-mañ: {{BOOK_NAME}}",
"no-book-found": "Neus levr ebet a glot gant an dezverkoù-se",
"url-not-found": "Neo ket bet kavet an URL \"{{url}}\" goulennet war ar servijer-mañ.",
"random-article-failure": "Chaous! Nhon eus ket gellet dibab ur pennad dre ziouer evidoch :(",
"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": "Disochoù <b>{{START}}-{{END}}</b> diwar <b>{{COUNT}}</b> evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Disoch ebet kavet evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "diouzh {{BOOK_TITLE}}",
"word-count": "{{COUNT}} a cherioù",
"library-button-text": "Mont dar bajenn degemer",
"home-button-text": "Mont da bajenn degemer \"{{BOOK_TITLE}}\"",
"random-page-button-text": "Mont dur bajenn dre zegouezh",
"searchbox-tooltip": "Klask '{{BOOK_TITLE}}'",
"powered-by-kiwix-html": "Lusket gant&nbsp;<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
View 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ɛ &nbsp;<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ŋ"
}

View File

@@ -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"
}

View File

@@ -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 nest 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 na é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
View 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"
}

View File

@@ -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
View 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"
}

View File

@@ -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",

View File

@@ -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": "Оди на случајно избрана страница",

View File

@@ -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"

View File

@@ -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": "При поддержке&nbsp;<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": "Неизвестная ошибка"
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"
}

View File

@@ -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": "预览"
}

View File

@@ -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": "前往隨機選取頁面",

View File

@@ -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
View 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);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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&amp;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&amp;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&amp;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&amp;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&amp;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&amp;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 &amp; 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&amp;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&amp;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&amp;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&amp;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&amp;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&amp;content=ted_en_business_2018-07" />
</entry>
<entry>
<title>Mythology &amp; 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&amp;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&amp;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);

View File

@@ -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");
}