mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-24 15:07:59 -05:00
Compare commits
97 Commits
smart_sugg
...
dirScan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42a2ce2534 | ||
|
|
3945dda5d0 | ||
|
|
d65dd859da | ||
|
|
d94d2c1e8a | ||
|
|
a20b135f80 | ||
|
|
6d520a8aa7 | ||
|
|
f82bfc068f | ||
|
|
e6335be897 | ||
|
|
1074e833b7 | ||
|
|
9da5fbad1e | ||
|
|
1869fb4e8e | ||
|
|
536198fa38 | ||
|
|
ca808718f7 | ||
|
|
b65074f961 | ||
|
|
8b7d1ef9ec | ||
|
|
8b0f01fa9b | ||
|
|
33f22eb966 | ||
|
|
55c13c3d24 | ||
|
|
2b1f556c20 | ||
|
|
e0cd5a1642 | ||
|
|
0a9ba9b678 | ||
|
|
db9607e55e | ||
|
|
592e22732e | ||
|
|
17f0ad2cf4 | ||
|
|
4928509991 | ||
|
|
c2df0a99fe | ||
|
|
cffca3ad85 | ||
|
|
0a2bebe7a3 | ||
|
|
bdb1f09884 | ||
|
|
f98b79348b | ||
|
|
2b8927e66e | ||
|
|
d0fb8214c3 | ||
|
|
d5894092fd | ||
|
|
dd09e3ce5f | ||
|
|
92954bbbe4 | ||
|
|
7a9edccbc5 | ||
|
|
e9e76e0901 | ||
|
|
ad9377083f | ||
|
|
d857b0f8f6 | ||
|
|
759d430232 | ||
|
|
e402dcabcb | ||
|
|
54bd29e3ed | ||
|
|
5c8aa240ad | ||
|
|
39672f0532 | ||
|
|
e0491adc85 | ||
|
|
286649e8c3 | ||
|
|
b799c0648b | ||
|
|
050906c1b2 | ||
|
|
f5e35b4c5d | ||
|
|
2a858dcc82 | ||
|
|
ac9be80369 | ||
|
|
d0a48cc9cc | ||
|
|
67b7e25494 | ||
|
|
2b4b90f8a3 | ||
|
|
208dd96edd | ||
|
|
51ffa31037 | ||
|
|
968d1c1067 | ||
|
|
c205a4703b | ||
|
|
8bff8d5891 | ||
|
|
94e51e363c | ||
|
|
25e03ce597 | ||
|
|
8a3c4c92e0 | ||
|
|
95fc478e37 | ||
|
|
26253ebf8f | ||
|
|
34c6a3bfab | ||
|
|
5c1f8de891 | ||
|
|
334ca0295e | ||
|
|
e2186cfb7b | ||
|
|
a4985c62c7 | ||
|
|
2f9deb0eaa | ||
|
|
b8d975068e | ||
|
|
222327586e | ||
|
|
699cbccf38 | ||
|
|
fdc3a715c4 | ||
|
|
3bfcc5108f | ||
|
|
6225b4608a | ||
|
|
0bc9a25179 | ||
|
|
9433f7cef9 | ||
|
|
eba66a391f | ||
|
|
fa6c93950c | ||
|
|
850e330461 | ||
|
|
eccb8db7b7 | ||
|
|
33bb0141c0 | ||
|
|
b3b4064ad6 | ||
|
|
2fdd2066cd | ||
|
|
4dfcfbe1fa | ||
|
|
e912f0520e | ||
|
|
c4ced73f7c | ||
|
|
c244d95a94 | ||
|
|
04d301d024 | ||
|
|
e415958ae9 | ||
|
|
f9b8789723 | ||
|
|
fe806396f9 | ||
|
|
5729b6540c | ||
|
|
33c83eec4b | ||
|
|
be69584637 | ||
|
|
2ba29f76e1 |
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@@ -11,23 +11,25 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-13
|
||||
target:
|
||||
- macos-aarch64-dyn
|
||||
- macos-x86_64-dyn
|
||||
- ios-arm64-dyn
|
||||
- ios-x86_64-dyn
|
||||
include:
|
||||
- target: macos-aarch64-dyn
|
||||
arch_name: arm64-apple-macos
|
||||
run_test: true
|
||||
- target: macos-x86_64-dyn
|
||||
arch_name: x86_64-apple-darwin
|
||||
run_test: true
|
||||
- target: ios-arm64-dyn
|
||||
arch_name: aarch64-apple-ios
|
||||
run_test: true
|
||||
run_test: false
|
||||
- target: ios-x86_64-dyn
|
||||
arch_name: x86-apple-ios-simulator
|
||||
run_test: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
run_test: false
|
||||
runs-on: macos-15
|
||||
|
||||
env:
|
||||
HOME: /Users/runner
|
||||
@@ -121,27 +123,24 @@ jobs:
|
||||
- linux-x86_64-dyn
|
||||
- android-arm
|
||||
- android-arm64
|
||||
image_variant: ['jammy']
|
||||
include:
|
||||
- target: linux-x86_64-static
|
||||
image_variant: focal
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
arch_name: linux-x86_64
|
||||
run_test: true
|
||||
coverage: true
|
||||
- target: linux-x86_64-dyn
|
||||
image_variant: focal
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
arch_name: linux-x86_64
|
||||
run_test: true
|
||||
coverage: true
|
||||
- target: android-arm
|
||||
image_variant: focal
|
||||
lib_postfix: '/arm-linux-androideabi'
|
||||
arch_name: arm-linux-androideabi
|
||||
run_test: false
|
||||
coverage: false
|
||||
- target: android-arm64
|
||||
image_variant: focal
|
||||
lib_postfix: '/aarch64-linux-android'
|
||||
arch_name: aarch64-linux-android
|
||||
run_test: false
|
||||
@@ -150,7 +149,7 @@ jobs:
|
||||
HOME: /home/runner
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:38"
|
||||
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:2025-06-07"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
9
.github/workflows/package.yml
vendored
9
.github/workflows/package.yml
vendored
@@ -21,7 +21,6 @@ jobs:
|
||||
# - debian-bullseye
|
||||
- ubuntu-noble
|
||||
- ubuntu-jammy
|
||||
- ubuntu-focal
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -89,14 +88,6 @@ jobs:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-focal
|
||||
if: matrix.distro == 'ubuntu-focal'
|
||||
name: Build package for ubuntu-focal
|
||||
id: build-ubuntu-focal
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Packages for ${{ matrix.distro }}
|
||||
|
||||
28
ChangeLog
28
ChangeLog
@@ -1,3 +1,31 @@
|
||||
libkiwix 14.1.1
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Fix regression for kiwix-serve --nosearchbar (@veloman-yunkan #1250)
|
||||
- Avoid results content interpretation... crash in fulltext search (@vighnesh-sawant #1241)
|
||||
- Fix for intermittent /content/blank.html errors (@veloman-yunkan #1249)
|
||||
|
||||
libkiwix 14.1.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Viewer detects & tracks intrapage navigation anchors too (@veloman-yunkan #1213)
|
||||
- Add support for catalog only mode (@veloman-yunkan #1219)
|
||||
- Add API which returns server access url (@vighnesh-sawant #1234)
|
||||
- Fix chrome searchbar placeholder text overflow (@aditii2712 #1185)
|
||||
- Fix magnet link queryStyring (@rgaudin #1160)
|
||||
- Improve chrome printing stylesheet (@kelson42 #1202)
|
||||
- Default white background (@kelson42 #1205)
|
||||
|
||||
* Other:
|
||||
- Switched to the new libzim illustrations API (@veloman-yunkan #1226)
|
||||
- Stop building Windows with DEBUG symbols in CI (@kelson42 #1165)
|
||||
- Update many things in the CI/CD (@kelson42 #1203 #1194 #1209 #1207 #1235)
|
||||
- Requires now libzim 9.4.0 (@kelson42 #1231)
|
||||
- Fix compilation for FreeBSD (@OICe2 #1173 #1174)
|
||||
- Wait up to 1s to let aria2c to start before complaining (@kelson42 #1169)
|
||||
|
||||
libkiwix 14.0.0
|
||||
===============
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ GNU/Linux, macOS, Android, iOS, ...).
|
||||
|
||||
[](https://download.kiwix.org/release/libkiwix/)
|
||||
[](https://github.com/kiwix/libkiwix/wiki/Repology)
|
||||
[](https://github.com/kiwix/libkiwix/actions?query=branch%3Amain)
|
||||
[](https://github.com/kiwix/libkiwix/actions?query=branch%3Amain)
|
||||
[](https://libkiwix.readthedocs.org/en/latest/?badge=latest)
|
||||
[](https://www.codefactor.io/repository/github/kiwix/libkiwix)
|
||||
[](https://codecov.io/gh/kiwix/libkiwix)
|
||||
|
||||
@@ -155,6 +155,15 @@ class Manager
|
||||
const std::string& url = "",
|
||||
const bool checkMetaData = false);
|
||||
|
||||
/**
|
||||
* Add all books from the directory tree into the library.
|
||||
*
|
||||
* @param path The path of the directory to scan.
|
||||
* @param verboseFlag Verbose logs flag.
|
||||
*/
|
||||
void addBooksFromDirectory(const std::string& path,
|
||||
const bool verboseFlag = false);
|
||||
|
||||
std::string writableLibraryPath;
|
||||
|
||||
bool m_hasSearchResult = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ headers = [
|
||||
'downloader.h',
|
||||
'search_renderer.h',
|
||||
'server.h',
|
||||
'spelling_correction.h',
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h',
|
||||
'tools.h',
|
||||
|
||||
@@ -50,7 +50,7 @@ class HumanReadableNameMapper : public NameMapper {
|
||||
std::map<std::string, std::string> m_nameToId;
|
||||
|
||||
public:
|
||||
HumanReadableNameMapper(kiwix::Library& library, bool withAlias);
|
||||
HumanReadableNameMapper(const kiwix::Library& library, bool withAlias);
|
||||
virtual ~HumanReadableNameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
|
||||
@@ -63,10 +63,13 @@ namespace kiwix
|
||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||
void setBlockExternalLinks(bool blockExternalLinks)
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
void setCatalogOnlyMode(bool enable) { m_catalogOnlyMode = enable; }
|
||||
void setContentServerUrl(std::string url) { m_contentServerUrl = url; }
|
||||
void setIpMode(IpMode mode) { m_ipMode = mode; }
|
||||
int getPort() const;
|
||||
IpAddress getAddress() const;
|
||||
IpMode getIpMode() const;
|
||||
std::vector<std::string> getServerAccessUrls() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Library> mp_library;
|
||||
@@ -83,6 +86,8 @@ namespace kiwix
|
||||
bool m_blockExternalLinks = false;
|
||||
IpMode m_ipMode = IpMode::AUTO;
|
||||
int m_ipConnectionLimit = 0;
|
||||
bool m_catalogOnlyMode = false;
|
||||
std::string m_contentServerUrl;
|
||||
std::unique_ptr<InternalServer> mp_server;
|
||||
};
|
||||
}
|
||||
|
||||
58
include/spelling_correction.h
Normal file
58
include/spelling_correction.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Veloman Yunkan
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SPELLING_CORRECTION_H
|
||||
#define KIWIX_SPELLING_CORRECTION_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace zim
|
||||
{
|
||||
class Archive;
|
||||
}
|
||||
|
||||
namespace Xapian
|
||||
{
|
||||
class Database;
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class SpellingsDB
|
||||
{
|
||||
public: // functions
|
||||
SpellingsDB(const zim::Archive& archive, std::filesystem::path cacheDirPath);
|
||||
~SpellingsDB();
|
||||
|
||||
SpellingsDB(const SpellingsDB& ) = delete;
|
||||
void operator=(const SpellingsDB& ) = delete;
|
||||
|
||||
std::vector<std::string> getSpellingCorrections(const std::string& word, uint32_t maxCount) const;
|
||||
|
||||
private: // data
|
||||
std::unique_ptr<Xapian::Database> impl_;
|
||||
};
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_SPELLING_CORRECTION_H
|
||||
17
meson.build
17
meson.build
@@ -1,5 +1,5 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '14.0.0',
|
||||
version : '14.1.1',
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
|
||||
|
||||
@@ -48,7 +48,14 @@ if (host_machine.system() == 'linux' and compiler.get_id() == 'gcc') or \
|
||||
else
|
||||
thread_dep = dependency('', required:false)
|
||||
endif
|
||||
|
||||
libicu_dep = dependency('icu-i18n', static:static_deps)
|
||||
if libicu_dep.version().version_compare('>= 76')
|
||||
libicu_deps = [libicu_dep, dependency('icu-uc', static:static_deps)]
|
||||
else
|
||||
libicu_deps = [libicu_dep]
|
||||
endif
|
||||
|
||||
pugixml_dep = dependency('pugixml', static:static_deps)
|
||||
libcurl_dep = dependency('libcurl', static:static_deps)
|
||||
microhttpd_dep = dependency('libmicrohttpd', static:static_deps)
|
||||
@@ -63,7 +70,7 @@ else
|
||||
error('Cannot found header mustache.hpp')
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version:['>=9.0.0', '<10.0.0'], static:static_deps)
|
||||
libzim_dep = dependency('libzim', version:['>=9.4.0', '<10.0.0'], static:static_deps)
|
||||
|
||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
|
||||
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
|
||||
@@ -86,7 +93,11 @@ if build_machine.system() == 'windows'
|
||||
endif
|
||||
|
||||
|
||||
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
|
||||
# Dependencies as string
|
||||
all_deps = [thread_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
|
||||
|
||||
# Dependencies as array
|
||||
all_deps += libicu_deps
|
||||
|
||||
inc = include_directories('include', extra_include)
|
||||
|
||||
|
||||
@@ -82,10 +82,11 @@ void Book::update(const zim::Archive& archive) {
|
||||
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
|
||||
|
||||
m_illustrations.clear();
|
||||
for ( const auto illustrationSize : archive.getIllustrationSizes() ) {
|
||||
for ( const auto& illustrationInfo : archive.getIllustrationInfos() ) {
|
||||
const auto illustration = std::make_shared<Illustration>();
|
||||
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationSize);
|
||||
illustration->width = illustration->height = illustrationSize;
|
||||
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationInfo);
|
||||
illustration->width = illustrationInfo.width;
|
||||
illustration->height = illustrationInfo.height;
|
||||
illustration->mimeType = illustrationItem.getMimetype();
|
||||
illustration->data = illustrationItem.getData();
|
||||
// NOTE: illustration->url is left uninitialized
|
||||
|
||||
@@ -30,7 +30,7 @@ std::string humanFriendlyTitle(std::string title)
|
||||
kainjow::mustache::object getLangTag(const std::vector<std::string>& bookLanguages) {
|
||||
std::string langShortString = "";
|
||||
std::string langFullString = "???";
|
||||
|
||||
|
||||
//if more than 1 languages then show "mul" else show the language
|
||||
if(bookLanguages.size() > 1) {
|
||||
std::vector<std::string> mulLanguages;
|
||||
@@ -44,7 +44,7 @@ kainjow::mustache::object getLangTag(const std::vector<std::string>& bookLanguag
|
||||
langShortString = bookLanguages[0];
|
||||
langFullString = getLanguageSelfName(langShortString);
|
||||
}
|
||||
|
||||
|
||||
kainjow::mustache::object langTag;
|
||||
langTag["langShortString"] = langShortString;
|
||||
langTag["langFullString"] = langFullString;
|
||||
@@ -130,6 +130,7 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
|
||||
RESOURCE::templates::no_js_library_page_html,
|
||||
kainjow::mustache::object{
|
||||
{"root", rootLocation},
|
||||
{"contentAccessUrl", onlyAsNonEmptyMustacheValue(contentAccessUrl)},
|
||||
{"books", booksData },
|
||||
{"searchQuery", searchQuery},
|
||||
{"languages", languages},
|
||||
|
||||
@@ -50,6 +50,13 @@ class LibraryDumper
|
||||
*/
|
||||
void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; }
|
||||
|
||||
/**
|
||||
* Set the URL for accessing book content
|
||||
*
|
||||
* @param url the URL of the /content endpoint of the content server
|
||||
*/
|
||||
void setContentAccessUrl(const std::string& url) { this->contentAccessUrl = url; }
|
||||
|
||||
/**
|
||||
* Set some informations about the search results.
|
||||
*
|
||||
@@ -58,10 +65,10 @@ class LibraryDumper
|
||||
* @param count the number of result of the current set (or page).
|
||||
*/
|
||||
void setOpenSearchInfo(int totalResult, int startIndex, int count);
|
||||
|
||||
|
||||
/**
|
||||
* Sets user default language
|
||||
*
|
||||
*
|
||||
* @param userLang the user language to be set
|
||||
*/
|
||||
void setUserLanguage(std::string userLang) { this->m_userLang = userLang; }
|
||||
@@ -81,6 +88,7 @@ class LibraryDumper
|
||||
const kiwix::NameMapper* const nameMapper;
|
||||
std::string libraryId;
|
||||
std::string rootLocation;
|
||||
std::string contentAccessUrl;
|
||||
std::string m_userLang;
|
||||
int m_totalResults;
|
||||
int m_startIndex;
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
#include "tools/pathTools.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <queue>
|
||||
#include <cctype>
|
||||
#include <algorithm>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -251,6 +259,58 @@ bool Manager::addBookFromPath(const std::string& pathToOpen,
|
||||
.empty());
|
||||
}
|
||||
|
||||
void Manager::addBooksFromDirectory(const std::string& path,
|
||||
const bool verboseFlag)
|
||||
{
|
||||
std::set<std::string> iteratedDirs;
|
||||
std::queue<std::string> dirQueue;
|
||||
dirQueue.push(fs::absolute(path).u8string());
|
||||
int totalBooksAdded = 0;
|
||||
if (verboseFlag)
|
||||
std::cout << "Adding books from the directory tree: " << dirQueue.front() << std::endl;
|
||||
|
||||
while (!dirQueue.empty()) {
|
||||
const auto currentPath = dirQueue.front();
|
||||
dirQueue.pop();
|
||||
if (verboseFlag)
|
||||
std::cout << "Visiting directory: " << currentPath << std::endl;
|
||||
for (const auto& dirEntry : fs::directory_iterator(currentPath)) {
|
||||
auto resolvedPath = dirEntry.path();
|
||||
if (fs::is_symlink(dirEntry)) {
|
||||
try {
|
||||
resolvedPath = fs::canonical(dirEntry.path());
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Could not resolve symlink " << resolvedPath.u8string() << " to a valid path. Skipping..." << std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const std::string pathString = resolvedPath.u8string();
|
||||
std::string resolvedPathExtension = resolvedPath.extension();
|
||||
std::transform(resolvedPathExtension.begin(), resolvedPathExtension.end(), resolvedPathExtension.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
if (fs::is_directory(resolvedPath)) {
|
||||
if (iteratedDirs.find(pathString) == iteratedDirs.end())
|
||||
dirQueue.push(pathString);
|
||||
else if (verboseFlag)
|
||||
std::cout << "Already iterated over " << pathString << ". Skipping..." << std::endl;
|
||||
} else if (resolvedPathExtension == ".zim" || resolvedPathExtension == ".zimaa") {
|
||||
if (!this->addBookFromPath(pathString, pathString, "", false)) {
|
||||
std::cerr << "Could not add " << pathString << " into the library." << std::endl;
|
||||
} else if (verboseFlag) {
|
||||
std::cout << "Added " << pathString << " into the library." << std::endl;
|
||||
totalBooksAdded++;
|
||||
}
|
||||
} else if (verboseFlag) {
|
||||
std::cout << "Skipped " << pathString << " - unsupported file type or permission denied." << std::endl;
|
||||
}
|
||||
}
|
||||
iteratedDirs.insert(currentPath);
|
||||
}
|
||||
|
||||
if (verboseFlag)
|
||||
std::cout << "Traversal completed. Total books added: " << totalBooksAdded << std::endl;
|
||||
}
|
||||
|
||||
bool Manager::readBookFromPath(const std::string& path, kiwix::Book* book)
|
||||
{
|
||||
std::string tmp_path = path;
|
||||
|
||||
@@ -31,6 +31,7 @@ kiwix_sources = [
|
||||
'server/internalServer_catalog.cpp',
|
||||
'server/i18n.cpp',
|
||||
'opds_catalog.cpp',
|
||||
'spelling_correction.cpp',
|
||||
'version.cpp'
|
||||
]
|
||||
kiwix_sources += lib_resources
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool withAlias) {
|
||||
for (auto& bookId: library.filter(kiwix::Filter().local(true).valid(true))) {
|
||||
HumanReadableNameMapper::HumanReadableNameMapper(const kiwix::Library& library, bool withAlias) {
|
||||
for (auto& bookId: library.filter(kiwix::Filter())) {
|
||||
auto& currentBook = library.getBookById(bookId);
|
||||
auto bookName = currentBook.getHumanReadableIdFromPath();
|
||||
m_idToName[bookId] = bookName;
|
||||
|
||||
@@ -51,24 +51,26 @@ typedef kainjow::mustache::list IllustrationInfo;
|
||||
IllustrationInfo getBookIllustrationInfo(const Book& book)
|
||||
{
|
||||
kainjow::mustache::list illustrations;
|
||||
if ( book.isPathValid() ) {
|
||||
for ( const auto& illustration : book.getIllustrations() ) {
|
||||
// For now, we are handling only sizexsize@1 illustration.
|
||||
// So we can simply pass one size to mustache.
|
||||
illustrations.push_back(kainjow::mustache::object{
|
||||
{"icon_size", to_string(illustration->width)},
|
||||
{"icon_mimetype", illustration->mimeType}
|
||||
});
|
||||
}
|
||||
for ( const auto& illustration : book.getIllustrations() ) {
|
||||
// For now, we are handling only sizexsize@1 illustration.
|
||||
// So we can simply pass one size to mustache.
|
||||
illustrations.push_back(kainjow::mustache::object{
|
||||
{"icon_size", to_string(illustration->width)},
|
||||
{"icon_mimetype", illustration->mimeType}
|
||||
});
|
||||
}
|
||||
return illustrations;
|
||||
}
|
||||
|
||||
std::string fullEntryXML(const Book& book, const std::string& rootLocation, const std::string& contentId)
|
||||
std::string fullEntryXML(const Book& book,
|
||||
const std::string& rootLocation,
|
||||
const std::string& contentAccessUrl,
|
||||
const std::string& contentId)
|
||||
{
|
||||
const auto bookDate = book.getDate() + "T00:00:00Z";
|
||||
const kainjow::mustache::object data{
|
||||
{"root", rootLocation},
|
||||
{"contentAccessUrl", onlyAsNonEmptyMustacheValue(contentAccessUrl)},
|
||||
{"id", book.getId()},
|
||||
{"name", book.getName()},
|
||||
{"title", book.getTitle()},
|
||||
@@ -105,7 +107,12 @@ std::string partialEntryXML(const Book& book, const std::string& rootLocation)
|
||||
return render_template(xmlTemplate, data);
|
||||
}
|
||||
|
||||
BooksData getBooksData(const Library* library, const NameMapper* nameMapper, const std::vector<std::string>& bookIds, const std::string& rootLocation, bool partial)
|
||||
BooksData getBooksData(const Library* library,
|
||||
const NameMapper* nameMapper,
|
||||
const std::vector<std::string>& bookIds,
|
||||
const std::string& rootLocation,
|
||||
const std::string& contentAccessUrl,
|
||||
bool partial)
|
||||
{
|
||||
BooksData booksData;
|
||||
for ( const auto& bookId : bookIds ) {
|
||||
@@ -114,7 +121,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con
|
||||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||||
const auto entryXML = partial
|
||||
? partialEntryXML(book, rootLocation)
|
||||
: fullEntryXML(book, rootLocation, contentId);
|
||||
: fullEntryXML(book, rootLocation, contentAccessUrl, contentId);
|
||||
booksData.push_back(kainjow::mustache::object{ {"entry", entryXML} });
|
||||
} catch ( const std::out_of_range& ) {
|
||||
// the book was removed from the library since its id was obtained
|
||||
@@ -129,7 +136,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con
|
||||
|
||||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||||
{
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, false);
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentAccessUrl, false);
|
||||
const kainjow::mustache::object template_data{
|
||||
{"date", gen_date_str()},
|
||||
{"root", rootLocation},
|
||||
@@ -147,7 +154,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const s
|
||||
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
||||
{
|
||||
const auto endpointRoot = rootLocation + "/catalog/v2";
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, partial);
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentAccessUrl, partial);
|
||||
|
||||
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
||||
const std::string url = endpoint + (query.empty() ? "" : "?" + query);
|
||||
@@ -172,7 +179,7 @@ std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
||||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||||
return XML_HEADER
|
||||
+ "\n"
|
||||
+ fullEntryXML(book, rootLocation, contentId);
|
||||
+ fullEntryXML(book, rootLocation, contentAccessUrl, contentId);
|
||||
}
|
||||
|
||||
std::string OPDSDumper::categoriesOPDSFeed() const
|
||||
|
||||
@@ -58,7 +58,7 @@ ParameterizedMessage searchResultsPageHeaderMsg(const std::string& searchPattern
|
||||
return ParameterizedMessage("search-results-page-header",
|
||||
{
|
||||
{"SEARCH_PATTERN", searchPattern},
|
||||
{"START", r.get("start")->string_value()},
|
||||
{"START", r.get("startLabel")->string_value()},
|
||||
{"END", r.get("end") ->string_value()},
|
||||
{"COUNT", r.get("count")->string_value()},
|
||||
}
|
||||
@@ -225,7 +225,8 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
|
||||
results.set("items", items);
|
||||
results.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
||||
results.set("start", kiwix::beautifyInteger(resultStart));
|
||||
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength-1, estimatedResultCount)));
|
||||
results.set("startLabel", kiwix::beautifyInteger(resultStart+1));
|
||||
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength, estimatedResultCount)));
|
||||
|
||||
// pagination
|
||||
auto pagination = buildPagination(
|
||||
|
||||
@@ -29,6 +29,22 @@
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string makeServerUrl(std::string host, int port, std::string root)
|
||||
{
|
||||
const int httpDefaultPort = 80;
|
||||
|
||||
if (port == httpDefaultPort) {
|
||||
return "http://" + host + root;
|
||||
} else {
|
||||
return "http://" + host + ":" + std::to_string(port) + root;
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
Server::Server(LibraryPtr library, std::shared_ptr<NameMapper> nameMapper) :
|
||||
mp_library(library),
|
||||
mp_nameMapper(nameMapper),
|
||||
@@ -53,8 +69,16 @@ bool Server::start() {
|
||||
m_blockExternalLinks,
|
||||
m_ipMode,
|
||||
m_indexTemplateString,
|
||||
m_ipConnectionLimit));
|
||||
return mp_server->start();
|
||||
m_ipConnectionLimit,
|
||||
m_catalogOnlyMode,
|
||||
m_contentServerUrl));
|
||||
if (mp_server->start()) {
|
||||
// this syncs m_addr of InternalServer and Server as they may diverge
|
||||
m_addr = mp_server->getAddress();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::stop() {
|
||||
@@ -67,12 +91,12 @@ void Server::stop() {
|
||||
void Server::setRoot(const std::string& root)
|
||||
{
|
||||
m_root = root;
|
||||
if (m_root[0] != '/') {
|
||||
m_root = "/" + m_root;
|
||||
}
|
||||
if (m_root.back() == '/') {
|
||||
m_root.erase(m_root.size() - 1);
|
||||
}
|
||||
while (!m_root.empty() && m_root.back() == '/')
|
||||
m_root.pop_back();
|
||||
|
||||
while (!m_root.empty() && m_root.front() == '/')
|
||||
m_root = m_root.substr(1);
|
||||
m_root = m_root.empty() ? m_root : "/" + m_root;
|
||||
}
|
||||
|
||||
void Server::setAddress(const std::string& addr)
|
||||
@@ -91,12 +115,12 @@ void Server::setAddress(const std::string& addr)
|
||||
|
||||
int Server::getPort() const
|
||||
{
|
||||
return mp_server->getPort();
|
||||
return m_port;
|
||||
}
|
||||
|
||||
IpAddress Server::getAddress() const
|
||||
{
|
||||
return mp_server->getAddress();
|
||||
return m_addr;
|
||||
}
|
||||
|
||||
IpMode Server::getIpMode() const
|
||||
@@ -104,4 +128,16 @@ IpMode Server::getIpMode() const
|
||||
return mp_server->getIpMode();
|
||||
}
|
||||
|
||||
std::vector<std::string> Server::getServerAccessUrls() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
if (!m_addr.addr.empty()) {
|
||||
result.push_back(makeServerUrl(m_addr.addr, m_port, m_root));
|
||||
}
|
||||
if (!m_addr.addr6.empty()) {
|
||||
result.push_back(makeServerUrl("[" + m_addr.addr6 + "]", m_port, m_root));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -99,16 +99,6 @@ bool ipAvailable(const std::string addr)
|
||||
return false;
|
||||
}
|
||||
|
||||
inline std::string normalizeRootUrl(std::string rootUrl)
|
||||
{
|
||||
while ( !rootUrl.empty() && rootUrl.back() == '/' )
|
||||
rootUrl.pop_back();
|
||||
|
||||
while ( !rootUrl.empty() && rootUrl.front() == '/' )
|
||||
rootUrl = rootUrl.substr(1);
|
||||
return rootUrl.empty() ? rootUrl : "/" + rootUrl;
|
||||
}
|
||||
|
||||
std::string
|
||||
fullURL2LocalURL(const std::string& fullUrl, const std::string& rootLocation)
|
||||
{
|
||||
@@ -125,9 +115,12 @@ std::string getSearchComponent(const RequestContext& request)
|
||||
return query.empty() ? query : "?" + query;
|
||||
}
|
||||
|
||||
Filter get_search_filter(const RequestContext& request, const std::string& prefix="")
|
||||
Filter get_search_filter(const RequestContext& request, const std::string& prefix="", bool catalogOnlyMode = false)
|
||||
{
|
||||
auto filter = kiwix::Filter().valid(true).local(true);
|
||||
auto filter = kiwix::Filter();
|
||||
if ( !catalogOnlyMode ) {
|
||||
filter.valid(true).local(true);
|
||||
}
|
||||
try {
|
||||
filter.query(request.get_argument(prefix+"q"));
|
||||
} catch (const std::out_of_range&) {}
|
||||
@@ -432,10 +425,12 @@ InternalServer::InternalServer(LibraryPtr library,
|
||||
bool blockExternalLinks,
|
||||
IpMode ipMode,
|
||||
std::string indexTemplateString,
|
||||
int ipConnectionLimit) :
|
||||
int ipConnectionLimit,
|
||||
bool catalogOnlyMode,
|
||||
std::string contentServerUrl) :
|
||||
m_addr(addr),
|
||||
m_port(port),
|
||||
m_root(normalizeRootUrl(root)),
|
||||
m_root(root),
|
||||
m_rootPrefixOfDecodedURL(m_root),
|
||||
m_nbThreads(nbThreads),
|
||||
m_multizimSearchLimit(multizimSearchLimit),
|
||||
@@ -451,7 +446,9 @@ InternalServer::InternalServer(LibraryPtr library,
|
||||
mp_nameMapper(nameMapper ? nameMapper : std::shared_ptr<NameMapper>(&defaultNameMapper, NoDelete())),
|
||||
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
|
||||
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
|
||||
m_customizedResources(new CustomizedResources)
|
||||
m_customizedResources(new CustomizedResources),
|
||||
m_catalogOnlyMode(catalogOnlyMode),
|
||||
m_contentServerUrl(contentServerUrl)
|
||||
{
|
||||
m_root = urlEncode(m_root);
|
||||
}
|
||||
@@ -709,12 +706,10 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
||||
return Response::build_redirect(contentUrl + query);
|
||||
} catch (std::exception& e) {
|
||||
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
|
||||
return HTTP500Response(request)
|
||||
+ ParameterizedMessage("non-translated-text", {{"MSG", e.what()}});
|
||||
return HTTP500Response(request, m_root, request.get_full_url(), e.what());
|
||||
} catch (...) {
|
||||
fprintf(stderr, "===== Unhandled unknown error\n");
|
||||
return HTTP500Response(request)
|
||||
+ nonParameterizedMessage("unknown-error");
|
||||
return HTTP500Response(request, m_root, request.get_full_url());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,6 +840,15 @@ std::string InternalServer::getNoJSDownloadPageHTML(const std::string& bookId, c
|
||||
);
|
||||
}
|
||||
|
||||
void InternalServer::setContentAccessUrl(LibraryDumper& libDumper) const
|
||||
{
|
||||
if ( !m_contentServerUrl.empty() ) {
|
||||
libDumper.setContentAccessUrl(m_contentServerUrl + "/content");
|
||||
} else if ( !m_catalogOnlyMode ) {
|
||||
libDumper.setContentAccessUrl(m_root + "/content");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& request)
|
||||
{
|
||||
const auto url = request.get_url();
|
||||
@@ -852,12 +856,13 @@ std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& req
|
||||
HTMLDumper htmlDumper(mp_library.get(), mp_nameMapper.get());
|
||||
htmlDumper.setRootLocation(m_root);
|
||||
htmlDumper.setLibraryId(getLibraryId());
|
||||
setContentAccessUrl(htmlDumper);
|
||||
auto userLang = request.get_user_language();
|
||||
htmlDumper.setUserLanguage(userLang);
|
||||
std::string content;
|
||||
|
||||
if (urlParts.size() == 1) {
|
||||
auto filter = get_search_filter(request);
|
||||
auto filter = get_search_filter(request, "", m_catalogOnlyMode);
|
||||
try {
|
||||
if (request.get_argument("category") == "") {
|
||||
filter.clearCategory();
|
||||
@@ -1006,11 +1011,11 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
|
||||
return response;
|
||||
}
|
||||
|
||||
const auto start = max(1u, request.get_optional_param("start", 1u));
|
||||
const auto start = max(0u, request.get_optional_param("start", 0u));
|
||||
const auto pageLength = getSearchPageSize(request);
|
||||
|
||||
/* Get the results */
|
||||
SearchRenderer renderer(search->getResults(start-1, pageLength), start,
|
||||
SearchRenderer renderer(search->getResults(start, pageLength), start,
|
||||
search->getEstimatedMatches());
|
||||
renderer.setSearchPattern(searchInfo.pattern);
|
||||
renderer.setSearchBookQuery(searchInfo.bookFilterQuery);
|
||||
@@ -1105,7 +1110,7 @@ std::vector<std::string>
|
||||
InternalServer::search_catalog(const RequestContext& request,
|
||||
kiwix::OPDSDumper& opdsDumper)
|
||||
{
|
||||
const auto filter = get_search_filter(request);
|
||||
const auto filter = get_search_filter(request, "", m_catalogOnlyMode);
|
||||
std::vector<std::string> bookIdsToDump = mp_library->filter(filter);
|
||||
const auto totalResults = bookIdsToDump.size();
|
||||
const long count = request.get_optional_param("count", 10L);
|
||||
@@ -1136,6 +1141,7 @@ const std::string CONTENT_CSP_HEADER =
|
||||
"allow-same-origin "
|
||||
"allow-modals "
|
||||
"allow-popups "
|
||||
"allow-popups-to-escape-sandbox "
|
||||
"allow-forms "
|
||||
"allow-downloads;";
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ class SearchInfo {
|
||||
|
||||
typedef kainjow::mustache::data MustacheData;
|
||||
class OPDSDumper;
|
||||
class LibraryDumper;
|
||||
|
||||
class InternalServer {
|
||||
public:
|
||||
@@ -106,7 +107,9 @@ class InternalServer {
|
||||
bool blockExternalLinks,
|
||||
IpMode ipMode,
|
||||
std::string indexTemplateString,
|
||||
int ipConnectionLimit);
|
||||
int ipConnectionLimit,
|
||||
bool catalogOnlyMode,
|
||||
std::string zimViewerURL);
|
||||
virtual ~InternalServer();
|
||||
|
||||
MHD_Result handlerCallback(struct MHD_Connection* connection,
|
||||
@@ -160,6 +163,8 @@ class InternalServer {
|
||||
std::string getLibraryId() const;
|
||||
|
||||
std::string getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const;
|
||||
OPDSDumper getOPDSDumper() const;
|
||||
void setContentAccessUrl(LibraryDumper& libDumper) const;
|
||||
|
||||
private: // types
|
||||
class LockableSuggestionSearcher;
|
||||
@@ -192,6 +197,9 @@ class InternalServer {
|
||||
|
||||
class CustomizedResources;
|
||||
std::unique_ptr<CustomizedResources> m_customizedResources;
|
||||
|
||||
const bool m_catalogOnlyMode;
|
||||
const std::string m_contentServerUrl;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -51,6 +51,15 @@ const std::string opdsMimeType[] = {
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
OPDSDumper InternalServer::getOPDSDumper() const
|
||||
{
|
||||
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
setContentAccessUrl(opdsDumper);
|
||||
return opdsDumper;
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& request)
|
||||
{
|
||||
if (m_verbose.load()) {
|
||||
@@ -80,9 +89,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
||||
}
|
||||
|
||||
zim::Uuid uuid;
|
||||
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
|
||||
std::vector<std::string> bookIdsToDump;
|
||||
if (url == "root.xml") {
|
||||
uuid = zim::Uuid::generate(host);
|
||||
@@ -158,9 +165,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
|
||||
{
|
||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
|
||||
const auto bookIds = search_catalog(request, opdsDumper);
|
||||
const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial);
|
||||
return ContentResponse::build(
|
||||
@@ -177,9 +182,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
||||
return UrlNotFoundResponse(request);
|
||||
}
|
||||
|
||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
|
||||
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
|
||||
return ContentResponse::build(
|
||||
opdsFeed,
|
||||
@@ -189,9 +192,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
|
||||
{
|
||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
|
||||
return ContentResponse::build(
|
||||
opdsDumper.categoriesOPDSFeed(),
|
||||
opdsMimeType[OPDS_NAVIGATION_FEED]
|
||||
@@ -200,9 +201,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
|
||||
{
|
||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
|
||||
return ContentResponse::build(
|
||||
opdsDumper.languagesOPDSFeed(),
|
||||
opdsMimeType[OPDS_NAVIGATION_FEED]
|
||||
|
||||
@@ -491,15 +491,30 @@ HTTP400Response::HTTP400Response(const RequestContext& request)
|
||||
*this += ParameterizedMessage("invalid-request", {{"url", requestUrl}});
|
||||
}
|
||||
|
||||
HTTP500Response::HTTP500Response(const RequestContext& request)
|
||||
: HTTPErrorResponse(request,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
"500-page-title",
|
||||
"500-page-heading",
|
||||
std::string(),
|
||||
/*includeKiwixResponseData=*/true)
|
||||
HTTP500Response::HTTP500Response(const RequestContext& request,
|
||||
const std::string& root,
|
||||
const std::string& urlPath,
|
||||
const std::string& errorText)
|
||||
: ContentResponseBlueprint(&request,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
"text/html; charset=utf-8",
|
||||
RESOURCE::templates::sexy500_html,
|
||||
/*includeKiwixResponseData=*/true)
|
||||
{
|
||||
*this += nonParameterizedMessage("500-page-text");
|
||||
auto pageParams = Data::Object{
|
||||
{"root", root },
|
||||
{"url_path", urlPath},
|
||||
{"PAGE_TITLE", Data::fromMsgId("500-page-title")},
|
||||
{"PAGE_HEADING", Data::fromMsgId("500-page-heading")},
|
||||
{"PAGE_TEXT", Data::fromMsgId("500-page-text")},
|
||||
{"500_img_text", Data::fromMsgId("500-img-text")},
|
||||
};
|
||||
|
||||
if ( !errorText.empty() ) {
|
||||
pageParams["error"] = errorText;
|
||||
}
|
||||
|
||||
*this->m_data = Data(pageParams);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> Response::build_416(size_t resourceLength)
|
||||
|
||||
@@ -180,9 +180,12 @@ struct HTTP400Response : HTTPErrorResponse
|
||||
explicit HTTP400Response(const RequestContext& request);
|
||||
};
|
||||
|
||||
struct HTTP500Response : HTTPErrorResponse
|
||||
struct HTTP500Response : ContentResponseBlueprint
|
||||
{
|
||||
explicit HTTP500Response(const RequestContext& request);
|
||||
HTTP500Response(const RequestContext& request,
|
||||
const std::string& root,
|
||||
const std::string& urlPath,
|
||||
const std::string& error = "");
|
||||
};
|
||||
|
||||
class ItemResponse : public Response {
|
||||
|
||||
108
src/spelling_correction.cpp
Normal file
108
src/spelling_correction.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Veloman Yunkan
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "spelling_correction.h"
|
||||
#include "zim/archive.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <xapian.h>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::vector<std::string> getAllTitles(const zim::Archive& a)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (const auto& entry : a.iterByPath() ) {
|
||||
result.push_back(entry.getTitle());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void createXapianDB(std::string path, const zim::Archive& archive)
|
||||
{
|
||||
const int flags = Xapian::DB_BACKEND_GLASS|Xapian::DB_CREATE;
|
||||
const auto tmpDbPath = path + ".tmp";
|
||||
Xapian::WritableDatabase db(tmpDbPath, flags);
|
||||
for (const auto& t : getAllTitles(archive)) {
|
||||
db.add_spelling(t);
|
||||
}
|
||||
db.commit();
|
||||
db.compact(path, Xapian::DBCOMPACT_SINGLE_FILE);
|
||||
db.close();
|
||||
std::filesystem::remove_all(tmpDbPath);
|
||||
}
|
||||
|
||||
std::string spellingsDBPathForZIMArchive(std::filesystem::path cacheDirPath, const zim::Archive& a)
|
||||
{
|
||||
// The version of spellings DB must be updated each time an important change
|
||||
// to the implementation is made that renders using the previous version
|
||||
// impossible or undesirable.
|
||||
const char SPELLINGS_DB_VERSION[] = "0.1";
|
||||
|
||||
std::ostringstream filename;
|
||||
filename << a.getUuid() << ".spellingsdb.v" << SPELLINGS_DB_VERSION;
|
||||
return (cacheDirPath / filename.str()).string();
|
||||
}
|
||||
|
||||
std::unique_ptr<Xapian::Database> openOrCreateXapianDB(std::filesystem::path cacheDirPath, const zim::Archive& archive)
|
||||
{
|
||||
const auto path = spellingsDBPathForZIMArchive(cacheDirPath, archive);
|
||||
try
|
||||
{
|
||||
return std::make_unique<Xapian::Database>(path);
|
||||
}
|
||||
catch (const Xapian::DatabaseOpeningError& )
|
||||
{
|
||||
createXapianDB(path, archive);
|
||||
return std::make_unique<Xapian::Database>(path);
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
SpellingsDB::SpellingsDB(const zim::Archive& archive, std::filesystem::path cacheDirPath)
|
||||
: impl_(openOrCreateXapianDB(cacheDirPath, archive))
|
||||
{
|
||||
}
|
||||
|
||||
SpellingsDB::~SpellingsDB()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::string> SpellingsDB::getSpellingCorrections(const std::string& word, uint32_t maxCount) const
|
||||
{
|
||||
if ( maxCount > 1 ) {
|
||||
throw std::runtime_error("More than one spelling correction was requested");
|
||||
}
|
||||
|
||||
std::vector<std::string> result;
|
||||
const auto term = impl_->get_spelling_suggestion(word, 3);
|
||||
if ( !term.empty() ) {
|
||||
result.push_back(term);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
@@ -13,6 +13,7 @@ skin/i18n/fr.json
|
||||
skin/i18n/ha.json
|
||||
skin/i18n/he.json
|
||||
skin/i18n/hi.json
|
||||
skin/i18n/hu.json
|
||||
skin/i18n/hy.json
|
||||
skin/i18n/ia.json
|
||||
skin/i18n/id.json
|
||||
|
||||
@@ -2,6 +2,7 @@ skin/caret.png
|
||||
skin/bittorrent.png
|
||||
skin/magnet.png
|
||||
skin/404.svg
|
||||
skin/500.svg
|
||||
skin/blocklink.svg
|
||||
skin/feed.svg
|
||||
skin/langSelector.svg
|
||||
@@ -14,6 +15,7 @@ skin/isotope.pkgd.min.js
|
||||
skin/index.js
|
||||
skin/autoComplete/autoComplete.min.js
|
||||
skin/error.css
|
||||
skin/print.css
|
||||
skin/kiwix.css
|
||||
skin/taskbar.css
|
||||
skin/index.css
|
||||
@@ -47,6 +49,7 @@ templates/viewer_settings.js
|
||||
templates/no_js_library_page.html
|
||||
templates/no_js_download.html
|
||||
templates/sexy404.html
|
||||
templates/sexy500.html
|
||||
opensearchdescription.xml
|
||||
ft_opensearchdescription.xml
|
||||
catalog_v2_searchdescription.xml
|
||||
|
||||
1
static/skin/500.svg
Normal file
1
static/skin/500.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.7 KiB |
@@ -3,6 +3,8 @@
|
||||
"authors": [
|
||||
"Asma",
|
||||
"Hamoudak",
|
||||
"Meno25",
|
||||
"Mohammed Qays",
|
||||
"Ravan",
|
||||
"محمد أحمد عبد الفتاح"
|
||||
]
|
||||
@@ -23,13 +25,14 @@
|
||||
"404-page-title": "المحتوى غير موجود",
|
||||
"404-page-heading": "لم يتم العثور عليه",
|
||||
"500-page-title": "خطأ داخلي بالخادم",
|
||||
"500-page-heading": "خطأ داخلي بالخادم",
|
||||
"500-page-heading": "عفواً، الصفحة لا تعمل.",
|
||||
"500-page-text": "لا يمكن إرسال المسار المطلوب بشكل صحيح:",
|
||||
"fulltext-search-unavailable": "البحث في كامل النص غير متاح",
|
||||
"no-search-results": "محرك البحث في كامل النص غير متوفر لهذا المحتوي.",
|
||||
"library-button-text": "توجه إلي صفحة الترحيب",
|
||||
"home-button-text": "انتقل إلى الصفحة الرئيسية لـ \"{{BOOK_TITLE}}\"",
|
||||
"home-button-text": "انتقل إلى الصفحة الرئيسة لـ '{{{BOOK_TITLE}}}'",
|
||||
"random-page-button-text": "اذهب إلى صفحة عشوائية",
|
||||
"searchbox-tooltip": "بحث \"{{BOOK_TITLE}}\"",
|
||||
"searchbox-tooltip": "ابحث عن '{{{BOOK_TITLE}}}'",
|
||||
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة.",
|
||||
"direct-download-alt-text": "التنزيل مباشرة عبر بروتوكول HTTP(S)",
|
||||
"hash-download-link-text": "المجموع الإختباري لخوارزمية SHA-256",
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Nokib Sarkar",
|
||||
"আফতাবুজ্জামান"
|
||||
]
|
||||
},
|
||||
"name": "বাংলা",
|
||||
"404-page-heading": "পাওয়া যায়নি",
|
||||
"new-404-page-title": "পাতা পাওয়া যায়নি",
|
||||
"new-404-page-heading": "ওহ! পাতা খুঁজে পাওয়া যায়নি",
|
||||
"404-img-text": "পাওয়া যায়নি!",
|
||||
"path-was-not-found": "অনুরোধ করা পথটি পাওয়া যায়নি:",
|
||||
"404-advice.p1": "আপনি যে বিষয়বস্তু খুঁজছেন তা এখনও উপলব্ধ থাকতে পারে, তবে এটি জিম ফাইলের মধ্যে অন্য কোনও জায়গায় অবস্থিত হতে পারে।",
|
||||
"404-advice.p4": "আপনি যে তথ্য খুঁজছেন তার সাথে সম্পর্কিত কীওয়ার্ড বা শিরোনাম খুঁজুন।",
|
||||
"500-page-title": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"500-page-heading": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"external-link-intro": "আপনি অনলাইনে যাওয়ার জন্য কিউইক্সের জিম রিডার ছেড়ে দিতে চলেছেন",
|
||||
"external-link-advice.p1": "আপনি যে লিঙ্কটিতে প্রবেশ করার চেষ্টা করছেন তা আপনার অফলাইন প্যাকেজের অংশ নয় এবং এর জন্য ইন্টারনেট সংযোগ প্রয়োজন।",
|
||||
"external-link-advice.p2": "আপনি যদি অনলাইনে যেতে পারেন, তাহলে লিঙ্কটি খোলার চেষ্টা করতে পারেন।",
|
||||
"external-link-advice.p3": "অন্যথায়, আপনি আপনার ব্রাউজারের পিছন বোতাম ব্যবহার করে আপনার ZIM-এর অফলাইন বিষয়বস্তুতে ফিরে যেতে পারেন।",
|
||||
"search-result-book-info": "{{BOOK_TITLE}} থেকে",
|
||||
"word-count": "{{COUNT}}টি শব্দ",
|
||||
"library-button-text": "স্বাগত পাতায় চলুন",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"IMayBeABitShy",
|
||||
"Justman10000",
|
||||
"Lucas Werkmeister",
|
||||
"MoritzMT20",
|
||||
"Rofiatmustapha12",
|
||||
"ThisCarthing"
|
||||
]
|
||||
@@ -37,9 +38,9 @@
|
||||
"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",
|
||||
"home-button-text": "Zur Hauptseite von '{{{BOOK_TITLE}}}' gehen",
|
||||
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
|
||||
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen",
|
||||
"searchbox-tooltip": "'{{{BOOK_TITLE}}}' durchsuchen",
|
||||
"confusion-of-tongues": "Zwei oder mehr Bücher unterschiedlicher Sprachen werden durchsucht, was zu unübersichtlichen Ergebnissen führen kann.",
|
||||
"welcome-page-overzealous-filter": "Keine Ergebnisse gefunden. Möchten Sie den <a href=\"{{URL}}\">Filter zurücksetzen</a>?",
|
||||
"powered-by-kiwix-html": "Angetrieben durch <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Jimkats",
|
||||
"Kelson",
|
||||
"Norhorn",
|
||||
"Ανώνυμος Βικιπαιδιστής"
|
||||
]
|
||||
},
|
||||
"name": "Αγγλικά",
|
||||
"suggest-full-text-search": "περιέχει '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Δεν υπάρχει τέτοιο βιβλίο: {{BOOK_NAME}}",
|
||||
"caution-warning": "Προσοχή!",
|
||||
"search-result-book-info": "από {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} λέξεις",
|
||||
"welcome-page-overzealous-filter": "Κανένα αποτέλεσμα. Θέλετε να <a href=\"{{URL}}\">επαναφέρετε το φίλτρο</a>;",
|
||||
"powered-by-kiwix-html": "Με την υποστήριξη by <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Αναζήτηση",
|
||||
@@ -19,10 +24,10 @@
|
||||
"direct-download-alt-text": "άμεση λήψη",
|
||||
"hash-download-alt-text": "λήψη αναγνωριστικού",
|
||||
"magnet-alt-text": "λήψη μαγνήτη",
|
||||
"torrent-download-link-text": "Αρχείο torrent",
|
||||
"torrent-download-alt-text": "λήψη torrent",
|
||||
"filter-by-tag": "Φίλτρο ανά ετικέτα \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Διακοπή φίλτρου ανά ετικέτα \"{{TAG}}\"",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Λήψη μέσω BitTorrent",
|
||||
"filter-by-tag": "Φιλτράρισμα κατά ετικέτα \"{{{TAG}}}\"",
|
||||
"stop-filtering-by-tag": "Διακοπή φιλτραρίσματος κατά ετικέτα \"{{{TAG}}}\"",
|
||||
"welcome-to-kiwix-server": "Καλώς ορίσατε στον διακομιστή Kiwix",
|
||||
"download-links-heading": "Λήψη συνδέσμων για <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Κατεβάστε το βιβλίο",
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
, "404-advice.p4": "Look for keywords or titles related to the information you're seeking"
|
||||
, "404-advice.p5": "This approach should help you locate the desired content, even if the original link isn't working properly."
|
||||
, "500-page-title" : "Internal Server Error"
|
||||
, "500-page-heading" : "Internal Server Error"
|
||||
, "500-page-text": "An internal server error occured. We are sorry about that :/"
|
||||
, "500-page-heading" : "Oops. Page isn't working."
|
||||
, "500-page-text": "The requested path cannot be properly delivered:"
|
||||
, "500-img-text": "Page isn't working"
|
||||
, "external-link-detected" : "External Link Detected"
|
||||
, "caution-warning" : "Caution!"
|
||||
, "external-link-intro" : "You are about to leave Kiwix's ZIM reader to go online to"
|
||||
@@ -46,9 +47,9 @@
|
||||
, "search-result-book-info": "from {{BOOK_TITLE}}"
|
||||
, "word-count": "{{COUNT}} words"
|
||||
, "library-button-text": "Go to welcome page"
|
||||
, "home-button-text": "Go to the main page of '{{BOOK_TITLE}}'"
|
||||
, "home-button-text": "Go to the main page of '{{{BOOK_TITLE}}}'"
|
||||
, "random-page-button-text": "Go to a randomly selected page"
|
||||
, "searchbox-tooltip": "Search '{{BOOK_TITLE}}'"
|
||||
, "searchbox-tooltip": "Search '{{{BOOK_TITLE}}}'"
|
||||
, "confusion-of-tongues": "Two or more books in different languages would participate in search, which may lead to confusing results."
|
||||
, "welcome-page-overzealous-filter": "No result. Would you like to <a href=\"{{URL}}\">reset filter</a>?"
|
||||
, "powered-by-kiwix-html": "Powered by <a href=\"https://kiwix.org\">Kiwix</a>"
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
"authors": [
|
||||
"AlexanderFF",
|
||||
"Fitoschido",
|
||||
"Laranxa",
|
||||
"Ovruni",
|
||||
"Sinopsistrans",
|
||||
"SpikeShroom",
|
||||
"Vis4valentine"
|
||||
]
|
||||
},
|
||||
"name": "español",
|
||||
"name": "Español",
|
||||
"suggest-full-text-search": "que contenga \"{{{SEARCH_TERMS}}}\"...",
|
||||
"no-such-book": "No existe el libro: {{BOOK_NAME}}",
|
||||
"too-many-books": "Demasiadas solicitudes de libros ({{NB_BOOKS}}) donde el límite es {{LIMIT}}",
|
||||
"no-book-found": "Ningún libro coincide con los criterios de selección.",
|
||||
"no-book-found": "Ningún libro coincide con los criterios de selección",
|
||||
"url-not-found": "La URL solicitada \"{{url}}\" no se encontró en este servidor.",
|
||||
"suggest-search": "Haga una búsqueda de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "¡Ups! Error al elegir un artículo aleatorio :(",
|
||||
@@ -26,9 +27,25 @@
|
||||
"400-page-heading": "Solicitud inválida",
|
||||
"404-page-title": "Contenido no encontrado",
|
||||
"404-page-heading": "No encontrado",
|
||||
"new-404-page-title": "Página no encontrada",
|
||||
"new-404-page-heading": "Ups. Página no encontrado.",
|
||||
"404-img-text": "¡No encontrado!",
|
||||
"path-was-not-found": "La pagina solicitada no se ha encontrado:",
|
||||
"404-advice.p1": "El contenido que está buscando podría estar disponible pero podría estar localizado en un lugar diferente dentro del archivo ZIM.",
|
||||
"404-advice.p2": "Por favor:",
|
||||
"404-advice.p3": "Intente usar la función de búsqueda para encontrar el contenido que desea",
|
||||
"404-advice.p4": "Mire por palabras clave o títulos relacionados a la información que está buscando",
|
||||
"404-advice.p5": "Este método debería ayudarle a localizar el contenido deseado incluso si el enlace original no funciona correctamente.",
|
||||
"500-page-title": "Error interno del servidor",
|
||||
"500-page-heading": "Error interno del servidor",
|
||||
"500-page-text": "Se produjo un error interno del servidor. Lo sentimos :/",
|
||||
"500-page-heading": "Ups. La página no funciona.",
|
||||
"500-page-text": "La pagina solicitada no se puede cargar correctamente:",
|
||||
"500-img-text": "La página no está funcionando",
|
||||
"external-link-detected": "Enlace externo detectado",
|
||||
"caution-warning": "¡Precaución!",
|
||||
"external-link-intro": "Usted está apunto de abandonar el lector ZIM de Kiwix para ir a",
|
||||
"external-link-advice.p1": "El enlace al que está tratando de acceder no es parte de su descarga sin conexión y requiere de conexión a internet.",
|
||||
"external-link-advice.p2": "Si usted puede conectarse en linea puede intentar abrir el enlace.",
|
||||
"external-link-advice.p3": "De lo contrario, puede volver al contenido sin conexión de su ZIM utilizando el botón atrás del navegador.",
|
||||
"fulltext-search-unavailable": "Búsqueda de texto completo no disponible",
|
||||
"no-search-results": "El motor de búsqueda de texto completo no está disponible para este contenido.",
|
||||
"search-results-page-title": "Buscar: {{SEARCH_PATTERN}}",
|
||||
@@ -37,9 +54,9 @@
|
||||
"search-result-book-info": "a partir de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} palabras",
|
||||
"library-button-text": "Ir a la página de bienvenida",
|
||||
"home-button-text": "Ir a la página principal de '{{BOOK_TITLE}}'",
|
||||
"home-button-text": "Ir a la página principal de '{{{BOOK_TITLE}}}'",
|
||||
"random-page-button-text": "Ir a una página seleccionada al azar",
|
||||
"searchbox-tooltip": "Buscar '{{BOOK_TITLE}}'",
|
||||
"searchbox-tooltip": "Buscar '{{{BOOK_TITLE}}}'",
|
||||
"confusion-of-tongues": "Dos o más libros en diferentes idiomas participarían en la búsqueda, lo que puede llevar a resultados confusos.",
|
||||
"welcome-page-overzealous-filter": "Sin resultados. ¿Quieres <a href=\"{{URL}}\">restablecer el filtro</a> ?",
|
||||
"powered-by-kiwix-html": "Desarrollado por <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
@@ -65,7 +82,15 @@
|
||||
"download-links-title": "Descargar libro",
|
||||
"preview-book": "Previsualizar",
|
||||
"unknown-error": "Error desconocido",
|
||||
"book-category.gutenberg": "Gutenberg",
|
||||
"book-category.iFixit": "iFixit",
|
||||
"book-category.mooc": "MOOC",
|
||||
"book-category.phet": "Phet",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.ted": "Ted",
|
||||
"book-category.vikidia": "Vikidia",
|
||||
"book-category.wikibooks": "Wikilibros",
|
||||
"book-category.wikihow": "wikiHow",
|
||||
"book-category.wikinews": "Wikinoticias",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
@@ -74,5 +99,6 @@
|
||||
"book-category.wikiversity": "Wikiversidad",
|
||||
"book-category.wikivoyage": "Wikiviajes",
|
||||
"book-category.wiktionary": "Wikcionario",
|
||||
"book-category.other": "Otros"
|
||||
"book-category.other": "Otros",
|
||||
"text-loading-content": "Cargando contenido"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"authors": [
|
||||
"MITO",
|
||||
"Nike",
|
||||
"Pyscowicz"
|
||||
"Pyscowicz",
|
||||
"Samoasambia"
|
||||
]
|
||||
},
|
||||
"name": "suomi",
|
||||
@@ -14,13 +15,22 @@
|
||||
"400-page-heading": "Virheellinen pyyntö",
|
||||
"404-page-title": "Sisältöä ei löytynyt",
|
||||
"404-page-heading": "Ei löytynyt",
|
||||
"new-404-page-title": "Sivua ei löytynyt",
|
||||
"new-404-page-heading": "Hupsista. Artikkelia ei löytynyt.",
|
||||
"404-img-text": "Ei löytynyt!",
|
||||
"path-was-not-found": "Pyydettyä polkua ei löytynyt:",
|
||||
"500-page-title": "Sisäinen palvelinvirhe",
|
||||
"500-page-heading": "Sisäinen palvelinvirhe",
|
||||
"500-page-heading": "Hupsista. Sivu ei toimi.",
|
||||
"500-img-text": "Sivu ei toimi",
|
||||
"external-link-detected": "Ulkoinen linkki havaittu",
|
||||
"caution-warning": "Huomio!",
|
||||
"external-link-intro": "Olet poistumassa Kiwixin ZIM-lukijasta siirtyäksesi verkkoon",
|
||||
"search-results-page-title": "Haku: {{SEARCH_PATTERN}}",
|
||||
"word-count": "{{COUNT}} sanaa",
|
||||
"library-button-text": "Siirry tervetulosivulle",
|
||||
"home-button-text": "Siirry kirjan '{{BOOK_TITLE}}' etusivulle",
|
||||
"home-button-text": "Siirry kirjan '{{{BOOK_TITLE}}}' etusivulle",
|
||||
"random-page-button-text": "Siirry satunnaiselle sivulle",
|
||||
"searchbox-tooltip": "Hae '{{BOOK_TITLE}}'",
|
||||
"searchbox-tooltip": "Hae kirjasta '{{{BOOK_TITLE}}}'",
|
||||
"search": "Hae",
|
||||
"book-filtering-all-categories": "Kaikki luokat",
|
||||
"book-filtering-all-languages": "Kaikki kielet",
|
||||
@@ -28,10 +38,21 @@
|
||||
"download": "Lataa",
|
||||
"magnet-link-text": "Magnet-linkki",
|
||||
"magnet-alt-text": "lataa magnet",
|
||||
"torrent-download-link-text": "Torrent-tiedosto",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "lataa torrent-tiedosto",
|
||||
"filter-by-tag": "Suodata tunnisteen ”{{TAG}}” mukaan",
|
||||
"download-links-title": "Lataa kirja",
|
||||
"preview-book": "Esikatsele",
|
||||
"unknown-error": "Tuntematon virhe"
|
||||
"unknown-error": "Tuntematon virhe",
|
||||
"book-category.wikibooks": "Wikikirjasto",
|
||||
"book-category.wikinews": "Wikiuutiset",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikisitaatit",
|
||||
"book-category.wikisource": "Wikiaineisto",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiopisto",
|
||||
"book-category.wikivoyage": "Wikimatkat",
|
||||
"book-category.wiktionary": "Wikisanakirja",
|
||||
"book-category.other": "Muu",
|
||||
"text-loading-content": "Ladataan sisältöä"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"authors": [
|
||||
"Adriendelucca",
|
||||
"Benoit74",
|
||||
"Derugon",
|
||||
"Gomoko",
|
||||
"Goombiis",
|
||||
"Melimeli",
|
||||
@@ -31,9 +32,25 @@
|
||||
"400-page-heading": "Requête non valide",
|
||||
"404-page-title": "Contenu non trouvé",
|
||||
"404-page-heading": "Non trouvé",
|
||||
"new-404-page-title": "Page non trouvée",
|
||||
"new-404-page-heading": "Oups. Page non trouvée.",
|
||||
"404-img-text": "Non trouvée !",
|
||||
"path-was-not-found": "Le chemin demandé n’a pas été trouvé :",
|
||||
"404-advice.p1": "Le contenu que vous recherchez peut toujours être disponible, mais il peut être positionné à un endroit différent dans le fichier ZIM.",
|
||||
"404-advice.p2": "Veuillez :",
|
||||
"404-advice.p3": "Essayer d’utiliser la fonction de recherche pour trouver le contenu que vous souhaitez",
|
||||
"404-advice.p4": "Rechercher des mots-clés ou des titres liés aux informations que vous recherchez",
|
||||
"404-advice.p5": "Cette approche devrait vous aider à localiser le contenu souhaité, même si le lien d’origine ne fonctionne pas correctement.",
|
||||
"500-page-title": "Erreur interne du serveur",
|
||||
"500-page-heading": "Erreur interne du serveur",
|
||||
"500-page-text": "Une erreur de serveur interne s'est produite. Nous en sommes désolés :/",
|
||||
"500-page-heading": "Oups, la page ne fonctionne pas.",
|
||||
"500-page-text": "Le chemin demandé ne peut pas être fourni correctement :",
|
||||
"500-img-text": "La page ne fonctionne pas",
|
||||
"external-link-detected": "Lien externe détecté",
|
||||
"caution-warning": "Attention !",
|
||||
"external-link-intro": "Vous êtes sur le point de quitter le lecteur ZIM de Kiwix pour vous connecter à",
|
||||
"external-link-advice.p1": "Le lien que vous essayez de suivre ne fait pas partie de votre forfait hors ligne et nécessite une connexion Internet.",
|
||||
"external-link-advice.p2": "Si vous pouvez vous connecter en ligne, vous pouvez essayer d’ouvrir le lien.",
|
||||
"external-link-advice.p3": "Vous pouvez également revenir au contenu hors ligne de votre ZIM en utilisant le bouton Retour de votre navigateur.",
|
||||
"fulltext-search-unavailable": "Recherche en texte intégral non disponible",
|
||||
"no-search-results": "Le moteur de recherche en texte intégral n’est pas disponible pour ce contenu.",
|
||||
"search-results-page-title": "Rechercher : {{SEARCH_PATTERN}}",
|
||||
@@ -42,9 +59,9 @@
|
||||
"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}} »",
|
||||
"home-button-text": "Aller à la page principale de « {{{BOOK_TITLE}}} »",
|
||||
"random-page-button-text": "Aller à une page sélectionnée aléatoirement",
|
||||
"searchbox-tooltip": "Rechercher « {{BOOK_TITLE}} »",
|
||||
"searchbox-tooltip": "Rechercher « {{{BOOK_TITLE}}} »",
|
||||
"confusion-of-tongues": "Deux livres ou plus dans des langues différentes participeraient à la recherche, ce qui pourrait conduire à des résultats confus.",
|
||||
"welcome-page-overzealous-filter": "Aucun résultat. Souhaitez-vous <a href=\"{{URL}}\">réinitialiser le filtre</a> ?",
|
||||
"powered-by-kiwix-html": "Propulsé par <a href=\"https://kiwix.org/\">Kiwix</a>",
|
||||
|
||||
@@ -22,9 +22,19 @@
|
||||
"400-page-heading": "בקשה בלתי־תקינה",
|
||||
"404-page-title": "התוכן לא נמצא",
|
||||
"404-page-heading": "לא נמצא",
|
||||
"new-404-page-title": "הדף לא נמצא",
|
||||
"new-404-page-heading": "אופס. הדף לא נמצא.",
|
||||
"404-img-text": "לא נמצא!",
|
||||
"path-was-not-found": "הנתיב המבוקש לא נמצא:",
|
||||
"404-advice.p2": "בבקשה:",
|
||||
"404-advice.p3": "לנסות להשתמש ביכולת החיפוש כדי למצוא את התוכן המבוקש",
|
||||
"500-page-title": "שגיאת שרת פנימית",
|
||||
"500-page-heading": "שגיאת שרת פנימית",
|
||||
"500-page-text": "אירעה שגיאת שרת פנימית. אנחנו מצטערים על זה :/",
|
||||
"500-page-heading": "אופס. הדף לא עובד.",
|
||||
"500-page-text": "אי אפשר למסור את הנתיב המבוקש כראוי:",
|
||||
"500-img-text": "הדף לא עובד",
|
||||
"external-link-detected": "התגלה קישור חיצוני",
|
||||
"caution-warning": "זהירות!",
|
||||
"external-link-advice.p2": "אם יש לך איך להתחבר לאינטרנט, אפשר לנסות לפתוח את הקישור.",
|
||||
"fulltext-search-unavailable": "חיפוש בטקסט מלא אינו זמין",
|
||||
"no-search-results": "מנוע החיפוש בטקסט מלא אינו זמין עבור התוכן הזה.",
|
||||
"search-results-page-title": "חיפוש: {{SEARCH_PATTERN}}",
|
||||
@@ -33,9 +43,9 @@
|
||||
"search-result-book-info": "מתוך {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} מילים",
|
||||
"library-button-text": "מעבר לדף הבית \"ברוך בואך\"",
|
||||
"home-button-text": "מעבר לדף הראשי של \"{{BOOK_TITLE}}\"",
|
||||
"home-button-text": "מעבר לדף הראשי של ‚{{{BOOK_TITLE}}}’",
|
||||
"random-page-button-text": "מעבר לדף שנבחר אקראית",
|
||||
"searchbox-tooltip": "חיפוש \"{{BOOK_TITLE}}\"",
|
||||
"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>",
|
||||
@@ -72,5 +82,6 @@
|
||||
"book-category.wikiversity": "ויקיברסיטה",
|
||||
"book-category.wikivoyage": "ויקימסע",
|
||||
"book-category.wiktionary": "ויקימילון",
|
||||
"book-category.other": "אחר"
|
||||
"book-category.other": "אחר",
|
||||
"text-loading-content": "התוכן נטען"
|
||||
}
|
||||
|
||||
39
static/skin/i18n/hu.json
Normal file
39
static/skin/i18n/hu.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Eukarióta",
|
||||
"Urbalazs"
|
||||
]
|
||||
},
|
||||
"name": "Magyar",
|
||||
"400-page-title": "Érvénytelen kérés",
|
||||
"400-page-heading": "Érvénytelen kérés",
|
||||
"404-page-title": "A tartalom nem található",
|
||||
"404-page-heading": "Nem található",
|
||||
"new-404-page-title": "Az oldal nem található",
|
||||
"new-404-page-heading": "Hoppá! Az oldal nem található.",
|
||||
"404-img-text": "Nem található!",
|
||||
"path-was-not-found": "A kért útvonal nem található:",
|
||||
"404-advice.p1": "A keresett tartalom továbbra is elérhető lehet, de előfordulhat, hogy más helyen található a ZIM-fájlon belül.",
|
||||
"search": "Keresés",
|
||||
"download": "Letöltés",
|
||||
"hash-download-link-text": "SHA-256 ellenőrzőösszeg",
|
||||
"hash-download-alt-text": "SHA-256 fájl-ellenőrzőösszeg megjelenítése",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"welcome-to-kiwix-server": "Üdvözli a Kiwix-kiszolgáló!",
|
||||
"download-links-heading": "Letöltési hivatkozások ehhez: <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Könyv letöltése",
|
||||
"preview-book": "Előnézet",
|
||||
"unknown-error": "Ismeretlen hiba",
|
||||
"book-category.wikibooks": "Wikikönyvek",
|
||||
"book-category.wikinews": "Wikihírek",
|
||||
"book-category.wikipedia": "Wikipédia",
|
||||
"book-category.wikiquote": "Wikidézet",
|
||||
"book-category.wikisource": "Wikiforrás",
|
||||
"book-category.wikispecies": "Wikifajok",
|
||||
"book-category.wikiversity": "Wikiegyetem",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wikiszótár",
|
||||
"book-category.other": "Egyéb",
|
||||
"text-loading-content": "Tartalom betöltése"
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"400-page-heading": "Անվավեր հարցում",
|
||||
"404-page-title": "Սխալ հասցե",
|
||||
"404-page-heading": "Սխալ հասցե",
|
||||
"500-img-text": "Էջը չի աշխատում",
|
||||
"library-button-text": "Գրադարանի էջ",
|
||||
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
|
||||
"random-page-button-text": "Բացել պատահական էջ",
|
||||
|
||||
@@ -22,9 +22,25 @@
|
||||
"400-page-heading": "Requesta invalide",
|
||||
"404-page-title": "Contento non trovate",
|
||||
"404-page-heading": "Non trovate",
|
||||
"new-404-page-title": "Pagina non trovate",
|
||||
"new-404-page-heading": "Pagina non trovate.",
|
||||
"404-img-text": "Non trovate!",
|
||||
"path-was-not-found": "Le percurso requestate non ha essite trovate:",
|
||||
"404-advice.p1": "Le contento que tu cerca pote esser ancora disponibile, ma illo pote esser situate alterubi in le file ZIM.",
|
||||
"404-advice.p2": "Per favor:",
|
||||
"404-advice.p3": "Tenta usar le function de recerca pro trovar le contento desirate",
|
||||
"404-advice.p4": "Cerca parolas-clave o titulos associate al information que tu cerca",
|
||||
"404-advice.p5": "Iste approche deberea adjutar te a localisar le contento desirate, mesmo si le ligamine original non functiona correctemente.",
|
||||
"500-page-title": "Error interne del servitor",
|
||||
"500-page-heading": "Error interne del servitor",
|
||||
"500-page-text": "Un error interne del servitor ha occurrite. Nos lo regretta :/",
|
||||
"500-page-heading": "Le pagina non functiona.",
|
||||
"500-page-text": "Le percurso requestate non pote esser livrate correctemente:",
|
||||
"500-img-text": "Le pagina non functiona",
|
||||
"external-link-detected": "Ligamine externe detegite",
|
||||
"caution-warning": "Attention!",
|
||||
"external-link-intro": "Tu es sur le puncto de quitar le lector ZIM de Kiwix pro connecter te al rete e visitar",
|
||||
"external-link-advice.p1": "Le ligamine al qual tu tenta acceder non face parte de nostre pacchetto de uso foras de linea e require un connexion a internet.",
|
||||
"external-link-advice.p2": "Si tu pote connecter te al rete, tu pote tentar aperir le ligamine.",
|
||||
"external-link-advice.p3": "Si non, tu pote retornar al contento de uso foras de linea de tu ZIM usante le button Retro del navigator.",
|
||||
"fulltext-search-unavailable": "Le recerca in texto complete es indisponibile",
|
||||
"no-search-results": "Le motor de recerca in texto complete non es disponibile pro iste contento.",
|
||||
"search-results-page-title": "Cercar: {{SEARCH_PATTERN}}",
|
||||
@@ -33,9 +49,9 @@
|
||||
"search-result-book-info": "de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} parolas",
|
||||
"library-button-text": "Ir al pagina de benvenita",
|
||||
"home-button-text": "Ir al pagina principal de ''{{BOOK_TITLE}}",
|
||||
"home-button-text": "Ir al pagina principal de ‘{{{BOOK_TITLE}}}’",
|
||||
"random-page-button-text": "Ir a un pagina seligite aleatorimente",
|
||||
"searchbox-tooltip": "Cercar '{{BOOK_TITLE}}'",
|
||||
"searchbox-tooltip": "Cercar in ‘{{{BOOK_TITLE}}}’",
|
||||
"confusion-of-tongues": "Duo o plus libros in differente linguas participarea in le recerca, lo que pote menar a resultatos confuse.",
|
||||
"welcome-page-overzealous-filter": "Nulle resultato. Vole tu <a href=\"{{URL}}\">reinitialisar le filtro</a>?",
|
||||
"powered-by-kiwix-html": "Actionate per <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
@@ -70,5 +86,6 @@
|
||||
"book-category.wikiversity": "Wikiversitate",
|
||||
"book-category.wikivoyage": "Wikiviage",
|
||||
"book-category.wiktionary": "Wiktionario",
|
||||
"book-category.other": "Altere"
|
||||
"book-category.other": "Altere",
|
||||
"text-loading-content": "Carga contento"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"Beta16",
|
||||
"Clorofolle",
|
||||
"Luca.favorido",
|
||||
"McDutchie"
|
||||
"McDutchie",
|
||||
"Wheelygay"
|
||||
]
|
||||
},
|
||||
"name": "italiano",
|
||||
@@ -26,17 +27,18 @@
|
||||
"new-404-page-heading": "Oops. Pagina non trovata.",
|
||||
"404-img-text": "Non trovato!",
|
||||
"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 :/",
|
||||
"500-page-heading": "Oops. La pagina non funziona.",
|
||||
"500-page-text": "Il percorso richiesto non può essere fornito correttamente:",
|
||||
"caution-warning": "Attenzione!",
|
||||
"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}}'",
|
||||
"home-button-text": "Vai alla pagina principale di '{{{BOOK_TITLE}}}'",
|
||||
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
||||
"searchbox-tooltip": "Cerca '{{BOOK_TITLE}}'",
|
||||
"searchbox-tooltip": "Cerca '{{{BOOK_TITLE}}}'",
|
||||
"welcome-page-overzealous-filter": "Nessun risultato. Vuoi <a href=\"{{URL}}\">reimpostare il filtro</a>?",
|
||||
"search": "Cerca",
|
||||
"book-filtering-all-categories": "Tutte le categorie",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"YeBoy371",
|
||||
"Ykhwong"
|
||||
]
|
||||
},
|
||||
@@ -21,9 +22,17 @@
|
||||
"400-page-heading": "잘못된 요청",
|
||||
"404-page-title": "내용이 없습니다",
|
||||
"404-page-heading": "찾을 수 없음",
|
||||
"new-404-page-title": "페이지를 찾을 수 없습니다",
|
||||
"new-404-page-heading": "이런. 페이지를 찾을 수 없습니다.",
|
||||
"404-img-text": "찾을 수 없습니다!",
|
||||
"path-was-not-found": "요청한 경로를 찾을 수 없습니다:",
|
||||
"404-advice.p3": "원하는 콘텐츠를 찾으려면 검색 기능을 사용해 보세요",
|
||||
"500-page-title": "내부 서버 오류",
|
||||
"500-page-heading": "내부 서버 오류",
|
||||
"500-page-text": "내부 서버 오류가 발생했습니다. 죄송합니다 :/",
|
||||
"500-page-heading": "죄송합니다. 문서가 동작하지 않습니다.",
|
||||
"500-page-text": "요청된 경로를 제대로 전달할 수 없습니다:",
|
||||
"500-img-text": "문서가 동작하지 않습니다",
|
||||
"external-link-detected": "외부 링크가 발견되었습니다",
|
||||
"caution-warning": "경고!",
|
||||
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
|
||||
"no-search-results": "이 콘텐츠에는 전문 검색 엔진을 사용할 수 없습니다.",
|
||||
"search-results-page-title": "검색: {{SEARCH_PATTERN}}",
|
||||
@@ -31,8 +40,9 @@
|
||||
"empty-search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b>의 결과가 없습니다",
|
||||
"search-result-book-info": "{{BOOK_TITLE}}에서",
|
||||
"word-count": "단어 {{COUNT}}개",
|
||||
"home-button-text": "'{{{BOOK_TITLE}}}'의 메인 페이지로 이동",
|
||||
"random-page-button-text": "무작위로 선택된 문서로 이동",
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' 검색",
|
||||
"searchbox-tooltip": "'{{{BOOK_TITLE}}}' 검색",
|
||||
"welcome-page-overzealous-filter": "결과가 없습니다. <a href=\"{{URL}}\">필터를 재설정</a>하시겠습니까?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a>에서 제공",
|
||||
"search": "검색",
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
"new-404-page-heading": "Ups. Säit net fonnt.",
|
||||
"404-img-text": "Net fonnt!",
|
||||
"500-page-title": "Interne Feeler um Server",
|
||||
"500-page-heading": "Interne Feeler um Server",
|
||||
"500-page-heading": "Ups. D'Säit funktionéiert net.",
|
||||
"500-page-text": "Et ass en interne Serverfeeler opgetrueden. Mir entschëllegen eis dofir :/",
|
||||
"500-img-text": "Säit funktionéiert net",
|
||||
"external-link-detected": "Externe Link entdeckt",
|
||||
"caution-warning": "Opgepasst!",
|
||||
"fulltext-search-unavailable": "Volltext-Sich net verfügbar",
|
||||
"search-results-page-title": "Sichen: {{SEARCH_PATTERN}}",
|
||||
@@ -28,9 +30,9 @@
|
||||
"empty-search-results-page-header": "Keng Resultater fonnt fir <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"search-result-book-info": "aus {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} Wierder",
|
||||
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
|
||||
"home-button-text": "Op d'Haaptsäit vun '{{{BOOK_TITLE}}}' goen",
|
||||
"random-page-button-text": "Gitt op eng zoufälleg gewielte Säit",
|
||||
"searchbox-tooltip": "No '{{BOOK_TITLE}}' sichen",
|
||||
"searchbox-tooltip": "No '{{{BOOK_TITLE}}}' sichen",
|
||||
"welcome-page-overzealous-filter": "Kee Resultat. Wëllt Dir <a href=\"{{URL}}\">de Filter zrécksetzen</a>?",
|
||||
"search": "Sichen",
|
||||
"book-filtering-all-categories": "All Kategorien",
|
||||
|
||||
@@ -22,9 +22,25 @@
|
||||
"400-page-heading": "Неважечко барање",
|
||||
"404-page-title": "Содржината не е најдена",
|
||||
"404-page-heading": "Не е најдено",
|
||||
"new-404-page-title": "Страницата не е пронајдена",
|
||||
"new-404-page-heading": "Ах! Страницата не е пронајдена.",
|
||||
"404-img-text": "Не е најдено!",
|
||||
"path-was-not-found": "Не ја најдов побараната патека:",
|
||||
"404-advice.p1": "Содржината што ја барате може сепак да е достапна, но може да се наоѓа на друго место во рамките на ZIM-податотеката.",
|
||||
"404-advice.p2": "Ве молиме:",
|
||||
"404-advice.p3": "Пробајте да ја употребите функцијата за пребарување за да ја најдете содржината што ви треба",
|
||||
"404-advice.p4": "Барајте клучни зборови или наслови поврзани со информациите што ви требаат",
|
||||
"404-advice.p5": "Овој приод треба да ви помогне да ја најдете саканата содржина, дури и ако изворната врска не работи правилно.",
|
||||
"500-page-title": "Внатрешна грешка во опслужувачот",
|
||||
"500-page-heading": "Внатрешна грешка во опслужувачот",
|
||||
"500-page-text": "Настана внатрешна грешка во опслужувачот. Жал ни е :/",
|
||||
"500-page-heading": "Страницата не работи.",
|
||||
"500-page-text": "Побараната патека не може правилно да се достави:",
|
||||
"500-img-text": "Страницата не работи.",
|
||||
"external-link-detected": "Најдена е надворешна врска",
|
||||
"caution-warning": "Внимание!",
|
||||
"external-link-intro": "На пат сте да го напуштите ZIM-читачот на Кивикс за да појдете на",
|
||||
"external-link-advice.p1": "Врската што пробувате да ја отворите не е дел од вашиот вонмрежен пакет и бара семрежна врска.",
|
||||
"external-link-advice.p2": "Ако можете да се поврзете со семрежнето, пробајте да ја отворите врската.",
|
||||
"external-link-advice.p3": "Во спротивно можете повторно да пробате да ја отворите вонмрежната содржина на вашиот ZIM стискајќи на копчето за враќање назад на вашиот прелистувач.",
|
||||
"fulltext-search-unavailable": "Целотекстното пребарување е недостапно",
|
||||
"no-search-results": "Погонот за целотекстно пребарување не е достапен за оваа содржина.",
|
||||
"search-results-page-title": "Пребарување: {{SEARCH_PATTERN}}",
|
||||
@@ -33,9 +49,9 @@
|
||||
"search-result-book-info": "од {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} зборови",
|
||||
"library-button-text": "Оди на воведната страница",
|
||||
"home-button-text": "Оди на главната страница на „{{BOOK_TITLE}}“",
|
||||
"home-button-text": "Оди на главната страница на „{{{BOOK_TITLE}}}“",
|
||||
"random-page-button-text": "Оди на случајно избрана страница",
|
||||
"searchbox-tooltip": "Пребарај го „{{BOOK_TITLE}}“",
|
||||
"searchbox-tooltip": "Пребарај по „{{{BOOK_TITLE}}}“",
|
||||
"confusion-of-tongues": "Во пребарувањето ќе учествуваат две или повеќе книги на различни јазици, што може да довете до збунувачки исход.",
|
||||
"welcome-page-overzealous-filter": "Нема исход. Дали би сакале да го <a href=\"{{URL}}\">поништите филтерот</a>?",
|
||||
"powered-by-kiwix-html": "Овозможено од <a href=\"https://kiwix.org\">Кивикс</a>",
|
||||
|
||||
@@ -25,9 +25,25 @@
|
||||
"400-page-heading": "Ongeldig verzoek",
|
||||
"404-page-title": "Inhoud niet gevonden",
|
||||
"404-page-heading": "Niet gevonden",
|
||||
"new-404-page-title": "Pagina niet gevonden",
|
||||
"new-404-page-heading": "Oeps. Pagina niet gevonden.",
|
||||
"404-img-text": "Niet gevonden!",
|
||||
"path-was-not-found": "Het gevraagde pad is niet gevonden:",
|
||||
"404-advice.p1": "De inhoud die u zoekt is mogelijk toch beschikbaar, maar kan zich ergens anders in het ZIM-bestand bevinden.",
|
||||
"404-advice.p2": "U kunt:",
|
||||
"404-advice.p3": "De zoekfunctie proberen om de gewenste inhoud te vinden",
|
||||
"404-advice.p4": "Trefwoorden of titels opzoeken die verband houden met de informatie die u zoekt",
|
||||
"404-advice.p5": "Met deze aanpak kunt u hopelijk de gewenste inhoud vinden, ook als de oorspronkelijke koppeling niet goed werkt.",
|
||||
"500-page-title": "Interne serverfout",
|
||||
"500-page-heading": "Interne serverfout",
|
||||
"500-page-text": "Er is een interne serverfout opgetreden. Dat spijt ons",
|
||||
"500-page-heading": "Oeps. De pagina werkt niet.",
|
||||
"500-page-text": "Het aangevraagde pad kan niet goed beschikbaar worden gesteld:",
|
||||
"500-img-text": "Pagina werkt niet",
|
||||
"external-link-detected": "Externe koppeling gedetecteerd",
|
||||
"caution-warning": "Voorzichtig!",
|
||||
"external-link-intro": "U staat op het punt de ZIM-lezer van Kiwix te verlaten om online te gaan naar",
|
||||
"external-link-advice.p1": "De koppeling die u probeert te openen, maakt geen deel uit van uw offline-pakket en vereist een internetverbinding.",
|
||||
"external-link-advice.p2": "Als u online kunt gaan, kunt u proberen de koppeling te openen.",
|
||||
"external-link-advice.p3": "Anders kunt u met de terugknop van uw browser terugkeren naar de offline-inhoud van uw ZIM.",
|
||||
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
|
||||
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
|
||||
"search-results-page-title": "Zoeken: {{SEARCH_PATTERN}}",
|
||||
@@ -36,9 +52,9 @@
|
||||
"search-result-book-info": "uit {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} woorden",
|
||||
"library-button-text": "Naar de welkomstpagina",
|
||||
"home-button-text": "Naar de hoofdpagina van ‘{{BOOK_TITLE}}’",
|
||||
"home-button-text": "Naar de hoofdpagina van ‘{{{BOOK_TITLE}}}’",
|
||||
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
|
||||
"searchbox-tooltip": "Naar ‘{{BOOK_TITLE}}’ zoeken",
|
||||
"searchbox-tooltip": "‘{{{BOOK_TITLE}}}’ doorzoeken",
|
||||
"confusion-of-tongues": "Er zouden twee of meer boeken in verschillende talen deelnemen aan de zoekopdracht, wat tot verwarrende resultaten kan leiden.",
|
||||
"welcome-page-overzealous-filter": "Geen resultaat. Wilt u <a href=\"{{URL}}\">het filter resetten</a>?",
|
||||
"powered-by-kiwix-html": "Mogelijk gemaakt door <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"500-page-title": "Erro interno do servidor",
|
||||
"500-page-heading": "Erro interno do servidor",
|
||||
"500-page-text": "Aconteceu um erro interno do servidor. Nós pedimos desculpas sobre isso :/",
|
||||
"caution-warning": "Cuidado!",
|
||||
"fulltext-search-unavailable": "Busca por texto completo está indisponível",
|
||||
"no-search-results": "O motor de busca de texto completo não está disponível para este conteúdo.",
|
||||
"search-results-page-title": "Buscar: {{SEARCH_PATTERN}}",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"500-page-title": "Title of the 500 error page",
|
||||
"500-page-heading": "Heading of the 500 error page",
|
||||
"500-page-text": "Text of the 500 error page",
|
||||
"500-img-text": "Fallback text for the image on the 500 error page",
|
||||
"external-link-detected": "Title & heading of the external link blocker page",
|
||||
"caution-warning": "Warning of action that shouldn't be carried out carelessly",
|
||||
"external-link-intro": "Message introducing the external link (to be followed by the actual link)",
|
||||
|
||||
@@ -23,9 +23,25 @@
|
||||
"400-page-heading": "Neveljaven zahtevek",
|
||||
"404-page-title": "Vsebine ni mogoče najti",
|
||||
"404-page-heading": "Ni najdeno",
|
||||
"new-404-page-title": "Stran ni bila najdena",
|
||||
"new-404-page-heading": "Ups. Stran ni bila najdena.",
|
||||
"404-img-text": "Ni najdeno!",
|
||||
"path-was-not-found": "Zahtevana pot ni bila najdena:",
|
||||
"404-advice.p1": "Vsebina, ki jo iščete, je mogoče še vedno na voljo, vendar se morda nahaja na drugem mestu v datoteki ZIM.",
|
||||
"404-advice.p2": "Prosimo:",
|
||||
"404-advice.p3": "Poskusite uporabiti funkcijo iskanja, da najdete želeno vsebino",
|
||||
"404-advice.p4": "Poiščite ključne besede ali naslove, povezane z informacijami, ki jih iščete",
|
||||
"404-advice.p5": "Ta pristop bi vam moral pomagati najti želeno vsebino, tudi če izvirna povezava ne deluje pravilno.",
|
||||
"500-page-title": "Notranja napaka strežnika",
|
||||
"500-page-heading": "Notranja napaka strežnika",
|
||||
"500-page-text": "Prišlo je do notranje napake strežnika. Žal nam je za to. :/",
|
||||
"500-page-heading": "Ups. Stran ne deluje.",
|
||||
"500-page-text": "Zahtevane poti ni mogoče pravilno dostaviti:",
|
||||
"500-img-text": "Stran ne deluje",
|
||||
"external-link-detected": "Zaznana zunanja povezava",
|
||||
"caution-warning": "Pozor!",
|
||||
"external-link-intro": "Zapustili boste bralnik ZIM v Kiwixu in se v internetu povezali z",
|
||||
"external-link-advice.p1": "Povezava, do katere poskušate dostopati, ni del vašega paketa za delo brez povezave in zahteva internetno povezavo.",
|
||||
"external-link-advice.p2": "Če se lahko povežete z internetom, lahko poskusite odpreti povezavo.",
|
||||
"external-link-advice.p3": "Na vsebino ZIM brez povezave se lahko vrnete tudi z gumbom za nazaj v brskalniku.",
|
||||
"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}}",
|
||||
@@ -33,10 +49,10 @@
|
||||
"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",
|
||||
"searchbox-tooltip": "Poiščite »{{BOOK_TITLE}}«",
|
||||
"library-button-text": "Pojdi na pozdravno stran",
|
||||
"home-button-text": "Pojdi na glavno stran »{{{BOOK_TITLE}}}«",
|
||||
"random-page-button-text": "Pojdi na naključno izbrano stran",
|
||||
"searchbox-tooltip": "Poiščite »{{{BOOK_TITLE}}}«",
|
||||
"confusion-of-tongues": "V iskanju bi bili uporabljeni dve ali več knjig v različnih jezikih, kar lahko pripelje do nejasnih zadetkov.",
|
||||
"welcome-page-overzealous-filter": "Ni zadetkov. Želite <a href=\"{{URL}}\">ponastaviti filter</a>?",
|
||||
"powered-by-kiwix-html": "Omogoča <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
@@ -46,20 +62,31 @@
|
||||
"count-of-matching-books": "{{COUNT}} knjiga(i/e)",
|
||||
"download": "Prenesi",
|
||||
"direct-download-link-text": "Neposredno",
|
||||
"direct-download-alt-text": "neposredni prenos",
|
||||
"hash-download-link-text": "Zgoščena vrednost SHA256",
|
||||
"hash-download-alt-text": "prenesi zgoščeno vrednost",
|
||||
"direct-download-alt-text": "Neposredni prenos prek HTTP(S)",
|
||||
"hash-download-link-text": "Kontrolna vsota SHA-256",
|
||||
"hash-download-alt-text": "Prikaz kontrolne vsote SHA-256 datoteke",
|
||||
"magnet-link-text": "Magnetna povezava",
|
||||
"magnet-alt-text": "prenesi magnet",
|
||||
"torrent-download-link-text": "Torrent datoteka",
|
||||
"torrent-download-alt-text": "prenesi torrent",
|
||||
"magnet-alt-text": "Prenos prek magnetne povezave",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Prenos prek BitTorrenta",
|
||||
"library-opds-feed-all-entries": "Knjižnični vir OPDS – Vsi vnosi",
|
||||
"filter-by-tag": "Filtriraj po oznaki »{{TAG}}«",
|
||||
"stop-filtering-by-tag": "Ustavi filtriranje po oznaki »{{TAG}}«",
|
||||
"filter-by-tag": "Filtriraj po oznaki »{{{TAG}}}«",
|
||||
"stop-filtering-by-tag": "Ustavi filtriranje po oznaki »{{{TAG}}}«",
|
||||
"library-opds-feed-parameterised": "Knjižnični vir OPDS – vnosi, ki se ujemajo z {{#LANG}}\nJezik: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategorija: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nOznaka: {{TAG}} {{/TAG}}{{#Q}}\nPoizvedba: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Pozdravljeni na strežniku Kiwix",
|
||||
"download-links-heading": "Povezave za prenos za <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Prenesi knjigo",
|
||||
"preview-book": "Predogled",
|
||||
"unknown-error": "Neznana napaka"
|
||||
"unknown-error": "Neznana napaka",
|
||||
"book-category.wikibooks": "Wikiknjige",
|
||||
"book-category.wikinews": "Wikinovice",
|
||||
"book-category.wikipedia": "Wikipedija",
|
||||
"book-category.wikiquote": "Wikinavedek",
|
||||
"book-category.wikisource": "Wikivir",
|
||||
"book-category.wikispecies": "Wikivrste",
|
||||
"book-category.wikiversity": "Wikiverza",
|
||||
"book-category.wikivoyage": "Wikipotovanje",
|
||||
"book-category.wiktionary": "Wikislovar",
|
||||
"book-category.other": "Drugo",
|
||||
"text-loading-content": "Nalaganje vsebine"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Apq",
|
||||
"Jopparn",
|
||||
"Larsa",
|
||||
"Rofiatmustapha12",
|
||||
@@ -25,9 +26,25 @@
|
||||
"400-page-heading": "Ogiltig begäran",
|
||||
"404-page-title": "Innehållet hittades inte",
|
||||
"404-page-heading": "Hittades inte",
|
||||
"new-404-page-title": "Sidan kunde inte hittas",
|
||||
"new-404-page-heading": "Hoppsan. Sidan hittades inte.",
|
||||
"404-img-text": "Hittades ej!",
|
||||
"path-was-not-found": "Den begärda sökvägen hittades ej:",
|
||||
"404-advice.p1": "Innehållet du letar efter kan fortfarande vara tillgängligt, men det kan finnas på en annan plats i ZIM-filen.",
|
||||
"404-advice.p2": "Vänligen:",
|
||||
"404-advice.p3": "Försök att använda sökfunktionen för att hitta det innehåll du vill ha",
|
||||
"404-advice.p4": "Leta efter nyckelord eller titlar relaterade till den information du söker",
|
||||
"404-advice.p5": "Den här metoden bör hjälpa dig att hitta önskat innehåll, även om den ursprungliga länken inte fungerar korrekt.",
|
||||
"500-page-title": "Internt serverfel",
|
||||
"500-page-heading": "Internt serverfel",
|
||||
"500-page-text": "Ett internt serverfel uppstod. Vi ber om ursäkt för det :/",
|
||||
"500-page-heading": "Hoppsan. Sidan fungerar inte.",
|
||||
"500-page-text": "Den begärda sökvägen kan inte levereras korrekt:",
|
||||
"500-img-text": "Sidan fungerar ej",
|
||||
"external-link-detected": "Extern länk upptäckt",
|
||||
"caution-warning": "Varning!",
|
||||
"external-link-intro": "Du är på väg att lämna Kiwix ZIM-läsare för att gå online till",
|
||||
"external-link-advice.p1": "Länken du försöker komma åt är inte en del av ditt offlinepaket och kräver en internetanslutning.",
|
||||
"external-link-advice.p2": "Om du kan gå online kan du försöka öppna länken.",
|
||||
"external-link-advice.p3": "Du kan annars återgå till ditt ZIM-innehåll offline genom att använda webbläsarens bakåtknapp.",
|
||||
"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}}",
|
||||
@@ -36,9 +53,9 @@
|
||||
"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}}\"",
|
||||
"home-button-text": "Gå till huvudsidan för '{{{BOOK_TITLE}}}'",
|
||||
"random-page-button-text": "Gå till en slumpmässigt utvald sida",
|
||||
"searchbox-tooltip": "Sök efter \"{{BOOK_TITLE}}\"",
|
||||
"searchbox-tooltip": "Sök '{{{BOOK_TITLE}}}'",
|
||||
"confusion-of-tongues": "Två eller fler böcker på olika språk skulle delta i sökningen, vilket kan ge förvirrande resultat.",
|
||||
"welcome-page-overzealous-filter": "Inga resultat. Vill du <a href=\"{{URL}}\">återställa filtret</a>?",
|
||||
"powered-by-kiwix-html": "Drivs av <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
@@ -73,5 +90,6 @@
|
||||
"book-category.wikiversity": "Wikiversity",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionary",
|
||||
"book-category.other": "Övriga"
|
||||
"book-category.other": "Övriga",
|
||||
"text-loading-content": "Laddar innehåll"
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
, "404-advice.p3": "[I18N TESTING] Check the spelling of the URL path"
|
||||
, "404-advice.p4": "[I18N TESTING] Press the dice button"
|
||||
, "404-advice.p5": "Good luck! [I18N TESTING]"
|
||||
, "500-page-title" : "[I18N] Internal Server Error [TESTING]"
|
||||
, "500-page-heading" : "Oops. [I18N] Page isn't [TESTING] working."
|
||||
, "500-page-text": "The requested path [I18N TESTING] cannot be properly delivered:"
|
||||
, "500-img-text": "Page [I18N] isn't [TESTING] working"
|
||||
, "external-link-detected" : "External [I18] Link [TESTING] Detected"
|
||||
, "caution-warning" : "[I18N] C5n! [TESTING]"
|
||||
, "external-link-intro" : "[I18N TESTING] The following link may lead you to a place from which you won't ever be able to return"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Hedda",
|
||||
"Rofiatmustapha12"
|
||||
"Rofiatmustapha12",
|
||||
"SaldırganSincap"
|
||||
]
|
||||
},
|
||||
"name": "Türkçe",
|
||||
@@ -23,8 +24,8 @@
|
||||
"404-page-title": "içerik bulunamadı",
|
||||
"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 :/",
|
||||
"500-page-heading": "Üzgünüz. Sayfa çalışmıyor.",
|
||||
"500-page-text": "İstenen yol düzgün bir şekilde teslim edilemiyor:",
|
||||
"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}}",
|
||||
@@ -33,9 +34,9 @@
|
||||
"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",
|
||||
"home-button-text": "'{{{BOOK_TITLE}}}' ana sayfasına git",
|
||||
"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",
|
||||
@@ -45,16 +46,16 @@
|
||||
"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",
|
||||
"direct-download-alt-text": "Doğrudan HTTP(S) üzerinden indir",
|
||||
"hash-download-link-text": "SHA-256 sağlama toplamı",
|
||||
"hash-download-alt-text": "SHA-256 dosya toplam kontrolünü görüntüle",
|
||||
"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",
|
||||
"magnet-alt-text": "Mıknatıs bağlantısıyla indir",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "BitTorrent üzerinden 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",
|
||||
"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ı",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Cyanjiang",
|
||||
"GuoPC",
|
||||
"IceButBin",
|
||||
"Kichin",
|
||||
"Peterxy12",
|
||||
"Prmsh",
|
||||
"StarrySky",
|
||||
"Sunai",
|
||||
@@ -28,9 +30,25 @@
|
||||
"400-page-heading": "无效请求",
|
||||
"404-page-title": "未找到内容",
|
||||
"404-page-heading": "未找到",
|
||||
"new-404-page-title": "找不到页面",
|
||||
"new-404-page-heading": "哎呀。页面未找到。",
|
||||
"404-img-text": "未找到!",
|
||||
"path-was-not-found": "未找到请求的路径:",
|
||||
"404-advice.p1": "您正在寻找的内容可能仍然可用,但它可能位于 ZIM 文件中的不同位置。",
|
||||
"404-advice.p2": "请:",
|
||||
"404-advice.p3": "尝试使用搜索功能来查找您想要的内容",
|
||||
"404-advice.p4": "查找与你正在寻找的信息相关的关键字或标题",
|
||||
"404-advice.p5": "即使原始链接无法正常工作,这种方法也应该可以帮助您找到所需的内容。",
|
||||
"500-page-title": "内部服务器错误",
|
||||
"500-page-heading": "内部服务器错误",
|
||||
"500-page-text": "内部服务器出现错误。真的十分抱歉 (;ŏ﹏ŏ~)",
|
||||
"500-page-heading": "哎呀。页面无法正常工作。",
|
||||
"500-page-text": "请求的路径无法正确传递:",
|
||||
"500-img-text": "页面无法正常工作",
|
||||
"external-link-detected": "检测到外部链接",
|
||||
"caution-warning": "警告!",
|
||||
"external-link-intro": "你即将离开Kiwix的ZIM阅读器并打开网页",
|
||||
"external-link-advice.p1": "你试图访问的链接不在你的离线包中,需要联网才能访问。",
|
||||
"external-link-advice.p2": "如果您可以上网,您可以尝试打开该链接。",
|
||||
"external-link-advice.p3": "您也可以使用浏览器的后退按钮返回 ZIM 的离线内容。",
|
||||
"fulltext-search-unavailable": "全文搜索不可用",
|
||||
"no-search-results": "全文搜索引擎不适用于该内容。",
|
||||
"search-results-page-title": "搜索:{{SEARCH_PATTERN}}",
|
||||
@@ -39,9 +57,9 @@
|
||||
"search-result-book-info": "来自{{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} 个字",
|
||||
"library-button-text": "转到欢迎页面",
|
||||
"home-button-text": "转到“{{BOOK_TITLE}}”的主页",
|
||||
"home-button-text": "转到“{{{BOOK_TITLE}}}”的主页",
|
||||
"random-page-button-text": "前往随机选择的页面",
|
||||
"searchbox-tooltip": "搜索“{{BOOK_TITLE}}”",
|
||||
"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>提供技术支持",
|
||||
|
||||
@@ -23,9 +23,25 @@
|
||||
"400-page-heading": "無效請求",
|
||||
"404-page-title": "查無內容",
|
||||
"404-page-heading": "查無頁面",
|
||||
"new-404-page-title": "找不到頁面",
|
||||
"new-404-page-heading": "哎呀,找不到頁面。",
|
||||
"404-img-text": "找不到!",
|
||||
"path-was-not-found": "找不到請求路徑。",
|
||||
"404-advice.p1": "您正在尋找的內容可能仍然可用,只是可能位於 ZIM 檔案中的不同位置。",
|
||||
"404-advice.p2": "請:",
|
||||
"404-advice.p3": "嘗試使用搜尋功能來尋找您想要的內容",
|
||||
"404-advice.p4": "查看與您正在尋找資訊相關的關鍵字或標題",
|
||||
"404-advice.p5": "即使原始連結無法正常運作,這種方法應該可以幫助您發現到所需的內容。",
|
||||
"500-page-title": "內部伺服器錯誤",
|
||||
"500-page-heading": "內部伺服器錯誤",
|
||||
"500-page-text": "內部伺服器發生錯誤。對此我們深感抱歉:/",
|
||||
"500-page-heading": "哎呀,頁面無法正常運作。",
|
||||
"500-page-text": "請求的路徑無法正確傳遞:",
|
||||
"500-img-text": "頁面無法正常運作",
|
||||
"external-link-detected": "偵測到外部連結",
|
||||
"caution-warning": "注意!",
|
||||
"external-link-intro": "您即將離開 Kiwix 的 ZIM 閱讀器,並上網到",
|
||||
"external-link-advice.p1": "您嘗試存取的連結不在離線套件裡,需要網路連線。",
|
||||
"external-link-advice.p2": "如果您可以連上網路,您可以嘗試開啟該連結。",
|
||||
"external-link-advice.p3": "您也可以使用瀏覽器的返回按鈕跳回到 ZIM 離線內容。",
|
||||
"fulltext-search-unavailable": "全文搜尋無效",
|
||||
"no-search-results": "全文搜尋引擎不適用此內容。",
|
||||
"search-results-page-title": "搜尋:{{SEARCH_PATTERN}}",
|
||||
@@ -34,9 +50,9 @@
|
||||
"search-result-book-info": "來自{{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}}個字",
|
||||
"library-button-text": "前往歡迎首頁",
|
||||
"home-button-text": "前往「{{BOOK_TITLE}}」的首頁",
|
||||
"home-button-text": "前往「{{{BOOK_TITLE}}}」的首頁",
|
||||
"random-page-button-text": "前往隨機選取頁面",
|
||||
"searchbox-tooltip": "在{{BOOK_TITLE}}搜尋",
|
||||
"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> 提供技術支援",
|
||||
@@ -53,10 +69,10 @@
|
||||
"magnet-alt-text": "透過磁力連結下載",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "透過 BitTorrent 下載",
|
||||
"library-opds-feed-all-entries": "圖書館 OPDS 摘要 - 所有項目",
|
||||
"library-opds-feed-all-entries": "文庫 OPDS 摘要 - 所有項目",
|
||||
"filter-by-tag": "依標籤「{{{TAG}}}」篩選",
|
||||
"stop-filtering-by-tag": "停止依標籤「{{{TAG}}}」篩選",
|
||||
"library-opds-feed-parameterised": "圖書館 OPDS 摘要 - 項目符合 {{#LANG}}\n語言:{{LANG}} {{/LANG}}{{#CATEGORY}}\n分類:{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n標籤:{{TAG}} {{/TAG}}{{#Q}}\n查詢:{{Q}} {{/Q}}",
|
||||
"library-opds-feed-parameterised": "文庫 OPDS 摘要 - 項目符合 {{#LANG}}\n語言:{{LANG}} {{/LANG}}{{#CATEGORY}}\n分類:{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n標籤:{{TAG}} {{/TAG}}{{#Q}}\n查詢:{{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "歡迎來到 Kiwix 伺服器",
|
||||
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
|
||||
"download-links-title": "下載書籍",
|
||||
|
||||
@@ -152,8 +152,21 @@
|
||||
: '';
|
||||
}
|
||||
|
||||
function addBookPreviewLink(html, bookXml) {
|
||||
const bookContentLink = bookXml.querySelector('link[type="text/html"]');
|
||||
if ( !bookContentLink )
|
||||
return html;
|
||||
|
||||
const urlComponents = bookContentLink.getAttribute('href').split('/');
|
||||
// bookContentLink URL = ROOT_URL/content/BOOK_NAME
|
||||
const bookName = urlComponents.pop();
|
||||
urlComponents.pop(); // drop 'content' component
|
||||
const viewerLink = urlComponents.join('/') + `/viewer#${bookName}`;
|
||||
|
||||
return `<a class="book__link" href="${viewerLink}" data-hover="Preview">${html}</a>`;
|
||||
}
|
||||
|
||||
function generateBookHtml(book, sort = false) {
|
||||
const link = book.querySelector('link[type="text/html"]').getAttribute('href');
|
||||
let iconUrl;
|
||||
book.querySelectorAll('link[rel="http://opds-spec.org/image/thumbnail"]').forEach(link => {
|
||||
if (link.getAttribute('type').split(';')[1] == 'width=48' && !iconUrl) {
|
||||
@@ -183,9 +196,6 @@
|
||||
} catch {
|
||||
downloadLink = '';
|
||||
}
|
||||
const bookName = link.split('/').pop();
|
||||
const viewerLink = `${root}/viewer#${bookName}`;
|
||||
|
||||
const humanFriendlyZimSize = humanFriendlySize(zimSize);
|
||||
|
||||
const divTag = document.createElement('div');
|
||||
@@ -197,9 +207,7 @@
|
||||
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
|
||||
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
|
||||
|
||||
divTag.innerHTML = `
|
||||
<div class="book__wrapper">
|
||||
<a class="book__link" href="${viewerLink}" data-hover="Preview">
|
||||
let bookLinkWrapper = `
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" ${faviconAttr}></div>
|
||||
<div class="book__header">
|
||||
@@ -207,7 +215,11 @@
|
||||
</div>
|
||||
<div class="book__description" title="${description}">${description}</div>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
|
||||
divTag.innerHTML = `
|
||||
<div class="book__wrapper">
|
||||
${addBookPreviewLink(bookLinkWrapper, book)}
|
||||
<div class="book__meta">
|
||||
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
|
||||
|
||||
@@ -13,6 +13,7 @@ html {
|
||||
body {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.loader {
|
||||
@@ -101,6 +102,10 @@ body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#content_iframe {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
#uiLanguageSelector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ar",
|
||||
"self_name": "الإنجليزية",
|
||||
"translation_count": 43
|
||||
"translation_count": 44
|
||||
},
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 24
|
||||
"translation_count": 34
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
@@ -37,7 +37,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "en",
|
||||
"self_name": "English",
|
||||
"translation_count": 77
|
||||
"translation_count": 93
|
||||
},
|
||||
{
|
||||
"iso_code": "es",
|
||||
@@ -52,7 +52,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 68
|
||||
"translation_count": 84
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
@@ -62,22 +62,27 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 69
|
||||
"translation_count": 80
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
"self_name": "हिन्दी",
|
||||
"translation_count": 59
|
||||
},
|
||||
{
|
||||
"iso_code": "hu",
|
||||
"self_name": "Magyar",
|
||||
"translation_count": 32
|
||||
},
|
||||
{
|
||||
"iso_code": "hy",
|
||||
"self_name": "Հայերեն",
|
||||
"translation_count": 25
|
||||
"translation_count": 26
|
||||
},
|
||||
{
|
||||
"iso_code": "ia",
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 67
|
||||
"translation_count": 84
|
||||
},
|
||||
{
|
||||
"iso_code": "id",
|
||||
@@ -92,7 +97,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 54
|
||||
"translation_count": 58
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -102,7 +107,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ko",
|
||||
"self_name": "한국어",
|
||||
"translation_count": 64
|
||||
"translation_count": 73
|
||||
},
|
||||
{
|
||||
"iso_code": "ku-latn",
|
||||
@@ -112,12 +117,12 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "lb",
|
||||
"self_name": "Lëtzebuergesch",
|
||||
"translation_count": 43
|
||||
"translation_count": 48
|
||||
},
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 76
|
||||
"translation_count": 81
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
@@ -127,7 +132,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "nb",
|
||||
"self_name": "Engelsk",
|
||||
"translation_count": 50
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "nl",
|
||||
@@ -152,7 +157,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "pt-br",
|
||||
"self_name": "Português",
|
||||
"translation_count": 65
|
||||
"translation_count": 66
|
||||
},
|
||||
{
|
||||
"iso_code": "pt",
|
||||
@@ -162,7 +167,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ro",
|
||||
"self_name": "Engleză",
|
||||
"translation_count": 67
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
@@ -182,7 +187,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "skr-arab",
|
||||
"self_name": "سرائیکی",
|
||||
"translation_count": 31
|
||||
"translation_count": 33
|
||||
},
|
||||
{
|
||||
"iso_code": "sl",
|
||||
@@ -222,6 +227,6 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "zh-hant",
|
||||
"self_name": "繁體中文",
|
||||
"translation_count": 76
|
||||
"translation_count": 92
|
||||
}
|
||||
]
|
||||
3
static/skin/print.css
Normal file
3
static/skin/print.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#kiwixtoolbarwrapper {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -34,6 +34,12 @@
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
#kiwixsearchbox {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#kiwix_serve_taskbar_home_button button {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -10,6 +10,8 @@ let viewerState = {
|
||||
uiLanguage: 'en',
|
||||
};
|
||||
|
||||
const FULL_ROOT_URL = `${window.location.origin}${root}`;
|
||||
|
||||
function dropUserLang(query) {
|
||||
const q = new URLSearchParams(query);
|
||||
q.delete('userlang');
|
||||
@@ -265,11 +267,20 @@ function translateErrorPageIfNeeded() {
|
||||
translatePageInWindow(contentIframe.contentWindow);
|
||||
}
|
||||
|
||||
|
||||
let iframeLocationHref = null;
|
||||
|
||||
function handle_content_url_change() {
|
||||
const iframeLocation = contentIframe.contentWindow.location;
|
||||
|
||||
if ( iframeLocationHref == iframeLocation.href ||
|
||||
!iframeLocation.pathname.startsWith(root + '/content/') )
|
||||
return;
|
||||
|
||||
iframeLocationHref = iframeLocation.href;
|
||||
console.log('handle_content_url_change: ' + iframeLocation.href);
|
||||
document.title = contentIframe.contentDocument.title;
|
||||
const iframeContentUrl = iframeLocation.pathname;
|
||||
const iframeContentUrl = iframeLocation.pathname + iframeLocation.hash;
|
||||
const iframeContentQuery = iframeLocation.search;
|
||||
const newHash = iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery);
|
||||
history.replaceState(viewerState, null, makeURL(location.search, newHash));
|
||||
@@ -347,6 +358,12 @@ function onClickEvent(e) {
|
||||
if (target !== null && "href" in target) {
|
||||
const target_href = getRealHref(target);
|
||||
const target_url = new URL(target_href, iframeDocument.location);
|
||||
if ( target_url.href.startsWith(`${FULL_ROOT_URL}/viewer#`) &&
|
||||
!linkShouldBeOpenedInANewWindow(target, e) ) {
|
||||
contentIframe.contentWindow.parent.location = target_url;
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
const isExternalAppUrl = urlMustBeHandledByAnExternalApp(target_url);
|
||||
if ( (isExternalAppUrl && !viewerSettings.linkBlockingEnabled)
|
||||
|| goingToOpenALinkToAnUndisplayableResource(target_url) ) {
|
||||
@@ -398,25 +415,32 @@ this.Element && function(ElementPrototype) {
|
||||
}
|
||||
}(Element.prototype);
|
||||
|
||||
function setup_external_link_blocker() {
|
||||
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// End of external link blocking
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
let viewerSetupComplete = false;
|
||||
const internalUrlPrefix = `${FULL_ROOT_URL}/content/`;
|
||||
|
||||
function setup_chaperon_mode() {
|
||||
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
|
||||
const links = contentIframe.contentDocument.getElementsByTagName('a');
|
||||
for ( const a of links ) {
|
||||
// XXX: wombat's possible involvement with href not taken into account
|
||||
if ( a.hasAttribute('href') && a.href.startsWith(internalUrlPrefix) ) {
|
||||
const userUrl = a.href.substr(internalUrlPrefix.length);
|
||||
a.href = `${root}/viewer#${userUrl}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function on_content_load() {
|
||||
const loader = document.getElementById("kiwix__loader");
|
||||
|
||||
contentIframe.classList.remove("hidden");
|
||||
loader.style.display = "none";
|
||||
if ( viewerSetupComplete ) {
|
||||
handle_content_url_change();
|
||||
}
|
||||
setup_external_link_blocker();
|
||||
contentIframe.contentWindow.onhashchange = handle_content_url_change;
|
||||
setInterval(handle_content_url_change, 100);
|
||||
setup_chaperon_mode();
|
||||
}
|
||||
|
||||
function htmlDecode(input) {
|
||||
@@ -564,6 +588,7 @@ function setupViewer() {
|
||||
|
||||
const kiwixToolBarWrapper = document.getElementById('kiwixtoolbarwrapper');
|
||||
if ( ! viewerSettings.toolbarEnabled ) {
|
||||
finishViewerSetup();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -612,10 +637,13 @@ function updateUIText() {
|
||||
function finishViewerSetupOnceTranslationsAreLoaded()
|
||||
{
|
||||
updateUIText();
|
||||
finishViewerSetup();
|
||||
}
|
||||
|
||||
function finishViewerSetup()
|
||||
{
|
||||
handle_location_hash_change();
|
||||
|
||||
window.onhashchange = handle_location_hash_change;
|
||||
window.onpopstate = handle_history_state_change;
|
||||
|
||||
viewerSetupComplete = true;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
{{#icons}}<link rel="http://opds-spec.org/image/thumbnail"
|
||||
href="{{root}}/catalog/v2/illustration/{{{id}}}/?size={{icon_size}}"
|
||||
type="{{icon_mimetype}};width={{icon_size}};height={{icon_size}};scale=1"/>
|
||||
{{/icons}}<link type="text/html" href="{{root}}/content/{{{content_id}}}" />
|
||||
{{/icons}}{{#contentAccessUrl}}<link type="text/html" href="{{contentAccessUrl}}/{{{content_id}}}" />
|
||||
{{/contentAccessUrl}}
|
||||
<author>
|
||||
<name>{{author_name}}</name>
|
||||
</author>
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
<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>
|
||||
<script src="{{root}}/skin/isotope.pkgd.min.js?KIWIXCACHEID" defer></script>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link type="root" href="{{root}}">
|
||||
<title>{{translations.welcome-to-kiwix-server}}</title>
|
||||
<link
|
||||
type="text/css"
|
||||
@@ -108,7 +107,7 @@
|
||||
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
|
||||
{{#books}}
|
||||
<div class="book__wrapper">
|
||||
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
|
||||
{{#contentAccessUrl}}<a class="book__link" href="{{contentAccessUrl}}/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">{{/contentAccessUrl}}
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" {{faviconAttr}}></div>
|
||||
<div class="book__header">
|
||||
@@ -116,7 +115,7 @@
|
||||
</div>
|
||||
<div class="book__description" title="{{description}}">{{description}}</div>
|
||||
</div>
|
||||
</a>
|
||||
{{#contentAccessUrl}}</a>{{/contentAccessUrl}}
|
||||
<div class="book__meta">
|
||||
<div class="book__languageTag" title="{{langTag.langFullString}}" aria-label="{{langTag.langFullString}}">{{langTag.langShortString}}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">
|
||||
|
||||
@@ -117,10 +117,10 @@
|
||||
{{title}}
|
||||
</a>
|
||||
{{#snippet}}
|
||||
<cite>{{>snippet}}...</cite>
|
||||
<cite>{{{snippet}}}...</cite>
|
||||
{{/snippet}}
|
||||
{{#bookInfo}}
|
||||
<div class="book-title">{{bookInfo}}</div>
|
||||
<div class="book-title">{{{bookInfo}}}</div>
|
||||
{{/bookInfo}}
|
||||
{{#wordCountInfo}}
|
||||
<div class="informations">{{wordCountInfo}}</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<title>{{title}}</title>
|
||||
<link>{{absolutePath}}</link>
|
||||
{{#snippet}}
|
||||
<description>{{>snippet}}...</description>
|
||||
<description>{{{snippet}}}...</description>
|
||||
{{/snippet}}
|
||||
{{#bookTitle}}
|
||||
<book>
|
||||
|
||||
31
static/templates/sexy500.html
Normal file
31
static/templates/sexy500.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>{{PAGE_TITLE}}</title>
|
||||
<link type="text/css" href="{{root}}/skin/error.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<script>
|
||||
window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";
|
||||
window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="{{root}}/skin/500.svg?KIWIXCACHEID"
|
||||
alt="{{500_img_text}}"
|
||||
aria-label="{{500_img_text}}"
|
||||
title="{{500_img_text}}">
|
||||
</header>
|
||||
<section class="intro">
|
||||
<h1>{{PAGE_HEADING}}</h1>
|
||||
<p>{{PAGE_TEXT}}</p>
|
||||
<p><code>{{url_path}}</code></p>
|
||||
</section>
|
||||
{{#error}}
|
||||
<section class="advice">
|
||||
<p>{{error}}</p>
|
||||
</section>
|
||||
{{/error}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -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" />
|
||||
<link type="text/css" href="./skin/print.css?KIWIXCACHEID" media="print" 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>
|
||||
|
||||
143
test/data/create_zim_file_for_testing_spelling_correction
Executable file
143
test/data/create_zim_file_for_testing_spelling_correction
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
mydir=$(readlink -f "$(dirname "$0")")
|
||||
myname=$(basename "$0")
|
||||
cd "$mydir"
|
||||
|
||||
zimfilename='spelling_correction_test.zim'
|
||||
|
||||
rm -f "$zimfilename"
|
||||
|
||||
datadir=$(mktemp -d --tmpdir $myname.XXXXXX)
|
||||
function cleanup()
|
||||
{
|
||||
rm -rf "$datadir"
|
||||
}
|
||||
trap cleanup EXIT SIGINT SIGQUIT SIGHUP SIGTERM
|
||||
|
||||
generate_html_file()
|
||||
{
|
||||
local word="$1"
|
||||
local letter_count=${#word}
|
||||
local letters=""
|
||||
local i
|
||||
for (( i=0; i<letter_count; ++i ));
|
||||
do
|
||||
local l=${word:i:1}
|
||||
if (( i == letter_count - 1 ))
|
||||
then
|
||||
letters+="and '$l'"
|
||||
else
|
||||
letters+="'$l', "
|
||||
fi
|
||||
done
|
||||
|
||||
cat >"$word".html <<END
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>$word</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>'$word' is a word consisting of the letters $letters.</p>
|
||||
</body>
|
||||
</html>
|
||||
END
|
||||
}
|
||||
|
||||
generate_zim_file_data()
|
||||
{
|
||||
local titles=(
|
||||
"Abenteuer"
|
||||
"Applaus"
|
||||
"Assistent"
|
||||
"Attacke"
|
||||
"Bewandtnis"
|
||||
"Biene"
|
||||
"Botschafter"
|
||||
"Chaos"
|
||||
"Entgelt"
|
||||
"Entzündung"
|
||||
"Fahrradschloss"
|
||||
"Führerschein"
|
||||
"Gral"
|
||||
"Hierarchie"
|
||||
"Honig"
|
||||
"Impfung"
|
||||
"Kamera"
|
||||
"Konkurrenz"
|
||||
"Lachs"
|
||||
"Mond"
|
||||
"Pflaster"
|
||||
"Phänomen"
|
||||
"Prise"
|
||||
"Schirmmütze"
|
||||
"Sohn"
|
||||
"Stuhl"
|
||||
"Teller"
|
||||
"Thermoskanne"
|
||||
"Trog"
|
||||
"Umweltstandard"
|
||||
"Unfug"
|
||||
"Wohnzimmer"
|
||||
"Zunge"
|
||||
"aber"
|
||||
"abonnieren"
|
||||
"amtieren"
|
||||
"attestieren"
|
||||
"ausleeren"
|
||||
"beißen"
|
||||
"ebenfalls"
|
||||
"enttäuschen"
|
||||
"fort"
|
||||
"gefleckt"
|
||||
"gefährlich"
|
||||
"gestern"
|
||||
"gewähren"
|
||||
"hässlich"
|
||||
"konkurrieren"
|
||||
"kämmen"
|
||||
"lustig"
|
||||
"müssen"
|
||||
"nämlich"
|
||||
"runterfallen"
|
||||
"sanft"
|
||||
"schubsen"
|
||||
"seit"
|
||||
"vorgestern"
|
||||
"wahrscheinlich"
|
||||
|
||||
"Willkommen"
|
||||
|
||||
# Entries for demonstrating shortcomings of the PoC implementation
|
||||
"Lorem ipsum"
|
||||
"King"
|
||||
"Kong"
|
||||
)
|
||||
|
||||
local t
|
||||
(
|
||||
cd "$datadir"
|
||||
cp "$mydir"/../../static/skin/favicon/favicon-32x32.png favicon.png
|
||||
for t in "${titles[@]}";
|
||||
do
|
||||
generate_html_file "$t"
|
||||
done
|
||||
)
|
||||
}
|
||||
|
||||
generate_zim_file_data
|
||||
zimwriterfs --withoutFTIndex --dont-check-arguments \
|
||||
-w Willkommen.html \
|
||||
-I favicon.png \
|
||||
-l deu \
|
||||
-n spelling_correction_test \
|
||||
-t "Spelling corrections test" \
|
||||
-d "ZIM file for testing spelling corrections" \
|
||||
-c "Kiwix" \
|
||||
-p "Kiwix" \
|
||||
$datadir \
|
||||
"$zimfilename" \
|
||||
&& echo "$zimfilename was successfully created" \
|
||||
|| echo '!!! Failed to create' "$zimfilename" '!!!' >&2
|
||||
@@ -51,4 +51,21 @@
|
||||
size="556"
|
||||
favicon="faviconMimeType_attribute_is_absent"
|
||||
></book>
|
||||
<book
|
||||
id="inaccessiblezim"
|
||||
path="./nosuchzimfile.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/nosuchzimfile.zim"
|
||||
title="Catalog of all catalogs"
|
||||
description="Testing that running kiwix-serve without access to ZIM files doesn't lead to a catastrophe"
|
||||
language="cat"
|
||||
creator="Catherine of Catalonia"
|
||||
publisher="Caterpillar"
|
||||
date="2025-09-04"
|
||||
name="catalog_of_all_catalogs"
|
||||
tags="unittest;_category:cats"
|
||||
articleCount="12107"
|
||||
mediaCount="8"
|
||||
size="20250904"
|
||||
favicon="Catania Cathedral"
|
||||
></book>
|
||||
</library>
|
||||
|
||||
BIN
test/data/spelling_correction_test.zim
Normal file
BIN
test/data/spelling_correction_test.zim
Normal file
Binary file not shown.
@@ -18,9 +18,15 @@ protected:
|
||||
const int PORT = 8002;
|
||||
|
||||
protected:
|
||||
void resetServer(ZimFileServer::Options options) {
|
||||
void resetServer(ZimFileServer::Cfg cfg) {
|
||||
zfs1_.reset();
|
||||
zfs1_.reset(new ZimFileServer(PORT, options, "./test/library.xml"));
|
||||
zfs1_.reset(new ZimFileServer(PORT, cfg, "./test/library.xml"));
|
||||
}
|
||||
|
||||
void resetServer(ZimFileServer::Options options, std::string contentServerUrl="") {
|
||||
ZimFileServer::Cfg cfg(options);
|
||||
cfg.contentServerUrl = contentServerUrl;
|
||||
resetServer(cfg);
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
@@ -150,6 +156,30 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
"125952"\
|
||||
)
|
||||
|
||||
#define INACCESSIBLEZIMFILE_CATALOG_ENTRY \
|
||||
" <entry>\n" \
|
||||
" <id>urn:uuid:inaccessiblezim</id>\n" \
|
||||
" <title>Catalog of all catalogs</title>\n" \
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||
" <summary>Testing that running kiwix-serve without access to ZIM files doesn't lead to a catastrophe</summary>\n" \
|
||||
" <language>cat</language>\n" \
|
||||
" <name>catalog_of_all_catalogs</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category>cats</category>\n" \
|
||||
" <tags>unittest;_category:cats</tags>\n" \
|
||||
" <articleCount>12107</articleCount>\n" \
|
||||
" <mediaCount>8</mediaCount>\n" \
|
||||
" <link type=\"text/html\" href=\"/ROOT%23%3F/content/nosuchzimfile\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Catherine of Catalonia</name>\n" \
|
||||
" </author>\n" \
|
||||
" <publisher>\n" \
|
||||
" <name>Caterpillar</name>\n" \
|
||||
" </publisher>\n" \
|
||||
" <dc:issued>2025-09-04T00:00:00Z</dc:issued>\n" \
|
||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/nosuchzimfile.zim\" length=\"20736925696\" />\n" \
|
||||
" </entry>\n"
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_root_xml)
|
||||
{
|
||||
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/root.xml");
|
||||
@@ -560,6 +590,15 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||
<title>List of categories</title>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
|
||||
<entry>
|
||||
<title>cats</title>
|
||||
<link rel="subsection"
|
||||
href="/ROOT%23%3F/catalog/v2/entries?category=cats"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<content type="text">All entries with category of 'cats'.</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>jazz</title>
|
||||
<link rel="subsection"
|
||||
@@ -602,6 +641,16 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
<title>List of languages</title>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
|
||||
<entry>
|
||||
<title>català</title>
|
||||
<dc:language>cat</dc:language>
|
||||
<thr:count>1</thr:count>
|
||||
<link rel="subsection"
|
||||
href="/ROOT%23%3F/catalog/v2/entries?lang=cat"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>English</title>
|
||||
<dc:language>eng</dc:language>
|
||||
@@ -678,6 +727,49 @@ TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_entries_catalog_only_mode)
|
||||
{
|
||||
const std::string contentServerUrl = "https://demo.kiwix.org";
|
||||
const auto fixContentLinks = [=](std::string s) -> std::string {
|
||||
s = replace(s, "/ROOT%23%3F/content", contentServerUrl + "/content");
|
||||
return s;
|
||||
};
|
||||
resetServer(ZimFileServer::CATALOG_ONLY_MODE, contentServerUrl);
|
||||
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
|
||||
const std::string expectedOPDS =
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("")
|
||||
" <title>All Entries</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
"\n"
|
||||
+ fixContentLinks(CHARLES_RAY_CATALOG_ENTRY)
|
||||
+ fixContentLinks(INACCESSIBLEZIMFILE_CATALOG_ENTRY)
|
||||
+ fixContentLinks(RAY_CHARLES_CATALOG_ENTRY)
|
||||
+ fixContentLinks(UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY) +
|
||||
"</feed>\n";
|
||||
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expectedOPDS);
|
||||
|
||||
{ // test with empty rootLocation
|
||||
const auto fixRoot = [=](std::string s) -> std::string {
|
||||
s = replace(s, "/ROOT%23%3F/", "/");
|
||||
s = replace(s, "/ROOT%23%3F/", "/");
|
||||
return s;
|
||||
};
|
||||
|
||||
ZimFileServer::Cfg serverCfg;
|
||||
serverCfg.root = "";
|
||||
serverCfg.options = ZimFileServer::CATALOG_ONLY_MODE;
|
||||
serverCfg.contentServerUrl = contentServerUrl;
|
||||
resetServer(serverCfg);
|
||||
|
||||
const auto r = zfs1_->GET("/catalog/v2/entries");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body), fixRoot(expectedOPDS));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||
{
|
||||
{
|
||||
@@ -1185,11 +1277,10 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" <head>\n" \
|
||||
" <meta charset=\"UTF-8\" />\n" \
|
||||
" <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n" \
|
||||
" <link type=\"root\" href=\"/ROOT%23%3F\">\n" \
|
||||
" <title>Welcome to Kiwix Server</title>\n" \
|
||||
" <link\n" \
|
||||
" type=\"text/css\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=b4e29e64\"\n" \
|
||||
" rel=\"Stylesheet\"\n" \
|
||||
" />\n" \
|
||||
" <link\n" \
|
||||
@@ -1320,6 +1411,32 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define INACCESSIBLEZIMFILE_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/nosuchzimfile\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/inaccessiblezim/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Catalog of all catalogs</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"Testing that running kiwix-serve without access to ZIM files doesn't lead to a catastrophe\">Testing that running kiwix-serve without access to ZIM files doesn't lead to a catastrophe</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" title=\"català\" aria-label=\"català\">cat</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/nosuchzimfile\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define FINAL_HTML_TEXT \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
@@ -1332,6 +1449,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" <div class=\"kiwixNav__select\">\n" \
|
||||
" <select name=\"lang\" id=\"languageFilter\" class='kiwixNav__kiwixFilter filter' form=\"kiwixSearchForm\">\n" \
|
||||
" <option value=\"\" selected>All languages</option>\n" \
|
||||
" <option value=\"cat\">català</option>\n" \
|
||||
" <option value=\"eng\"" SELECTED_ENG ">English</option>\n" \
|
||||
" <option value=\"fra\">français</option>\n" \
|
||||
" <option value=\"rus\">русский</option>\n" \
|
||||
@@ -1340,6 +1458,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" <div class=\"kiwixNav__select\">\n" \
|
||||
" <select name=\"category\" id=\"categoryFilter\" class='kiwixNav__kiwixFilter filter' form=\"kiwixSearchForm\">\n" \
|
||||
" <option value=\"\">All categories</option>\n" \
|
||||
" <option value=\"cats\">Cats</option>\n" \
|
||||
" <option value=\"jazz\">Jazz</option>\n" \
|
||||
" <option value=\"wikipedia\">Wikipedia</option>\n" \
|
||||
" </select>\n" \
|
||||
@@ -1453,4 +1572,47 @@ TEST_F(LibraryServerTest, noJS) {
|
||||
EXPECT_EQ(r->body, RAY_CHARLES_UNCTZ_DOWNLOAD);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, noJS_catalogOnlyMode) {
|
||||
const std::string contentServerUrl = "https://demo.kiwix.org";
|
||||
const auto fixContentLinks = [=](std::string s) -> std::string {
|
||||
s = replace(s, "/ROOT%23%3F/content", contentServerUrl + "/content");
|
||||
return s;
|
||||
};
|
||||
|
||||
resetServer(ZimFileServer::CATALOG_ONLY_MODE, contentServerUrl);
|
||||
|
||||
auto r = zfs1_->GET("/ROOT%23%3F/nojs");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
|
||||
const std::string expectedHTML =
|
||||
HTML_PREAMBLE
|
||||
FILTERS_HTML("")
|
||||
HOME_BODY_TEXT("4")
|
||||
+ fixContentLinks(CHARLES_RAY_BOOK_HTML)
|
||||
+ fixContentLinks(INACCESSIBLEZIMFILE_BOOK_HTML)
|
||||
+ fixContentLinks(RAY_CHARLES_BOOK_HTML)
|
||||
+ fixContentLinks(RAY_CHARLES_UNCTZ_BOOK_HTML)
|
||||
+ FINAL_HTML_TEXT;
|
||||
|
||||
EXPECT_EQ(r->body, expectedHTML);
|
||||
|
||||
{ // test with empty rootLocation
|
||||
const auto fixRoot = [=](std::string s) -> std::string {
|
||||
s = replace(s, "/ROOT%23%3F/", "/");
|
||||
return s;
|
||||
};
|
||||
|
||||
ZimFileServer::Cfg serverCfg;
|
||||
serverCfg.root = "";
|
||||
serverCfg.options = ZimFileServer::CATALOG_ONLY_MODE;
|
||||
serverCfg.contentServerUrl = contentServerUrl;
|
||||
|
||||
resetServer(serverCfg);
|
||||
|
||||
auto r = zfs1_->GET("/nojs");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body, fixRoot(expectedHTML));
|
||||
}
|
||||
}
|
||||
|
||||
#undef EXPECT_SEARCH_RESULTS
|
||||
|
||||
@@ -88,6 +88,7 @@ TEST(Manager, reload)
|
||||
manager.reload({ "./test/library.xml" });
|
||||
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
|
||||
"charlesray",
|
||||
"inaccessiblezim",
|
||||
"raycharles",
|
||||
"raycharles_uncategorized"
|
||||
}));
|
||||
@@ -95,12 +96,14 @@ TEST(Manager, reload)
|
||||
lib->removeBookById("raycharles");
|
||||
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
|
||||
"charlesray",
|
||||
"inaccessiblezim",
|
||||
"raycharles_uncategorized"
|
||||
}));
|
||||
|
||||
manager.reload({ "./test/library.xml" });
|
||||
EXPECT_EQ(lib->getBooksIds(), kiwix::Library::BookIdCollection({
|
||||
"charlesray",
|
||||
"inaccessiblezim",
|
||||
"raycharles",
|
||||
"raycharles_uncategorized"
|
||||
}));
|
||||
|
||||
@@ -15,7 +15,8 @@ tests = [
|
||||
'server_helper',
|
||||
'lrucache',
|
||||
'i18n',
|
||||
'response'
|
||||
'response',
|
||||
'spelling_correction'
|
||||
]
|
||||
|
||||
if build_machine.system() != 'windows'
|
||||
@@ -42,6 +43,7 @@ if gtest_dep.found() and not meson.is_cross_build()
|
||||
'zimfile_raycharles_uncategorized.zim',
|
||||
'corner_cases#&.zim',
|
||||
'poor.zim',
|
||||
'spelling_correction_test.zim',
|
||||
'library.xml',
|
||||
'lib_for_server_search_test.xml',
|
||||
'customized_resources.txt',
|
||||
|
||||
170
test/server.cpp
170
test/server.cpp
@@ -65,19 +65,19 @@ const ResourceCollection resources200Compressible{
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=ae79e41a" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=cc456f1f" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=4e232c58" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css?cacheid=b4e29e64" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js?cacheid=bd23c4fb" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=42e90cb9" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=7f05bf6c" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=00e0fdf3" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
|
||||
@@ -86,7 +86,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=a83f0e13" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=08955948" },
|
||||
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/search" },
|
||||
|
||||
@@ -112,6 +112,8 @@ const ResourceCollection resources200Compressible{
|
||||
const ResourceCollection resources200Uncompressible{
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/404.svg" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/404.svg?cacheid=b6d648af" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/500.svg" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/500.svg?cacheid=32eb0f20" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/bittorrent.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/bittorrent.png?cacheid=4f5c6882" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/blank.html" },
|
||||
@@ -162,6 +164,8 @@ const ResourceCollection resources200Uncompressible{
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/search-icon.svg?cacheid=b10ae7ed" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search_results.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/print.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/print.css?cacheid=65b1c1d2" },
|
||||
|
||||
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Title" },
|
||||
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Description" },
|
||||
@@ -286,7 +290,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
|
||||
const std::vector<UrlAndExpectedResult> testData{
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/",
|
||||
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846"
|
||||
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=b4e29e64"
|
||||
href="/ROOT%23%3F/skin/index.css?cacheid=ae79e41a"
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625">
|
||||
@@ -297,10 +301,10 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=3948b846"
|
||||
<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=e9a10ac1" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=a83f0e13" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=08955948" 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=cc456f1f" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=4e232c58" defer></script>
|
||||
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
|
||||
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
|
||||
)EXPECTEDRESULT"
|
||||
@@ -327,13 +331,14 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cac
|
||||
},
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/viewer",
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=3948b846" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?cacheid=80d56607" rel="Stylesheet" />
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=b4e29e64" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?cacheid=42e90cb9" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=f2d376c4" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/print.css?cacheid=65b1c1d2" media="print" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
|
||||
<script type="module" src="./skin/i18n.js?cacheid=e9a10ac1" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=a83f0e13" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=7f05bf6c" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=08955948" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=00e0fdf3" defer></script>
|
||||
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
|
||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
@@ -1006,7 +1011,6 @@ std::string expectedSexy404ErrorHtml(const std::string& url)
|
||||
|
||||
TEST_F(ServerTest, HttpSexy404HtmlError)
|
||||
{
|
||||
using namespace TestingOfHtmlResponses;
|
||||
const std::vector<std::string> testUrls {
|
||||
// XXX: Nicer 404 error page no longer hints whether the error
|
||||
// XXX: is because of the missing book/ZIM-file or a missing article
|
||||
@@ -1209,35 +1213,98 @@ TEST_F(ServerTest, HttpXmlError)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, 500)
|
||||
std::string expectedSexy500ErrorHtml(const std::string& url,
|
||||
const std::string& error)
|
||||
{
|
||||
const std::string expectedBody = R"(<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
const auto urlWithoutQuery = replace(url, "\\?.*$", "");
|
||||
const auto htmlSafeUrl = htmlEscape(urlWithoutQuery);
|
||||
const auto jsSafeUrl = escapeJsString(urlWithoutQuery);
|
||||
|
||||
const std::string englishText[] = {
|
||||
"Internal Server Error",
|
||||
"Page isn't working",
|
||||
"Oops. Page isn't working.",
|
||||
"The requested path cannot be properly delivered:",
|
||||
};
|
||||
|
||||
const std::string translatedText[] = {
|
||||
"[I18N] Internal Server Error [TESTING]",
|
||||
"Page [I18N] isn't [TESTING] working",
|
||||
"Oops. [I18N] Page isn't [TESTING] working.",
|
||||
"The requested path [I18N TESTING] cannot be properly delivered:",
|
||||
};
|
||||
|
||||
const bool shouldTranslate = url.find("userlang=test") != std::string::npos;
|
||||
const std::string* const t = shouldTranslate ? translatedText : englishText;
|
||||
|
||||
return R"RAWSTRINGLITERAL(<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||
<title>Internal Server Error</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>)RAWSTRINGLITERAL" + t[0] + R"RAWSTRINGLITERAL(</title>
|
||||
<link type="text/css" href="/ROOT%23%3F/skin/error.css?cacheid=b3fa90cf" rel="Stylesheet" />
|
||||
<script>
|
||||
window.KIWIX_RESPONSE_TEMPLATE = )" + ERROR_HTML_TEMPLATE_JS_STRING + R"(;
|
||||
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "500-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "500-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "500-page-text", "params" : { } } }, { "p" : { "msgid" : "non-translated-text", "params" : { "MSG" : "Entry redirect_loop.html is a redirect entry." } } } ] };
|
||||
window.KIWIX_RESPONSE_TEMPLATE = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset="utf-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />\n <title>{{PAGE_TITLE}}</title>\n <link type="text/css" href="{{root}}/skin/error.css?cacheid=b3fa90cf" rel="Stylesheet" />\n <script>\n window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n </script>\n </head>\n <body>\n <header>\n <img src="{{root}}/skin/500.svg?cacheid=32eb0f20"\n alt="{{500_img_text}}"\n aria-label="{{500_img_text}}"\n title="{{500_img_text}}">\n </header>\n <section class="intro">\n <h1>{{PAGE_HEADING}}</h1>\n <p>{{PAGE_TEXT}}</p>\n <p><code>{{url_path}}</code></p>\n </section>\n{{#error}}\n <section class="advice">\n <p>{{error}}</p>\n </section>\n{{/error}}\n </body>\n</html>\n";
|
||||
window.KIWIX_RESPONSE_DATA = { "500_img_text" : { "msgid" : "500-img-text", "params" : { } }, "PAGE_HEADING" : { "msgid" : "500-page-heading", "params" : { } }, "PAGE_TEXT" : { "msgid" : "500-page-text", "params" : { } }, "PAGE_TITLE" : { "msgid" : "500-page-title", "params" : { } }, "error" : ")RAWSTRINGLITERAL"
|
||||
+ // inject the error
|
||||
escapeJsString(error) // inject the error
|
||||
+ // inject the error
|
||||
R"RAWSTRINGLITERAL(", "root" : "/ROOT%23%3F", "url_path" : ")RAWSTRINGLITERAL"
|
||||
+ // inject the URL
|
||||
jsSafeUrl // inject the URL
|
||||
+ // inject the URL
|
||||
R"RAWSTRINGLITERAL(" };
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>
|
||||
An internal server error occured. We are sorry about that :/
|
||||
</p>
|
||||
<p>
|
||||
Entry redirect_loop.html is a redirect entry.
|
||||
</p>
|
||||
<header>
|
||||
<img src="/ROOT%23%3F/skin/500.svg?cacheid=32eb0f20"
|
||||
alt=")RAWSTRINGLITERAL" + t[1] + R"RAWSTRINGLITERAL("
|
||||
aria-label=")RAWSTRINGLITERAL" + t[1] + R"RAWSTRINGLITERAL("
|
||||
title=")RAWSTRINGLITERAL" + t[1] + R"RAWSTRINGLITERAL(">
|
||||
</header>
|
||||
<section class="intro">
|
||||
<h1>)RAWSTRINGLITERAL" + t[2] + R"RAWSTRINGLITERAL(</h1>
|
||||
<p>)RAWSTRINGLITERAL" + t[3] + R"RAWSTRINGLITERAL(</p>
|
||||
<p><code>)RAWSTRINGLITERAL"
|
||||
+ // inject the URL
|
||||
htmlSafeUrl // inject the URL
|
||||
+ // inject the URL
|
||||
R"RAWSTRINGLITERAL(</code></p>
|
||||
</section>
|
||||
<section class="advice">
|
||||
<p>)RAWSTRINGLITERAL" + error + R"RAWSTRINGLITERAL(</p>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
)";
|
||||
)RAWSTRINGLITERAL";
|
||||
}
|
||||
|
||||
{
|
||||
const auto r = zfs1_->GET("/ROOT%23%3F/content/poor/A/redirect_loop.html");
|
||||
EXPECT_EQ(r->status, 500);
|
||||
EXPECT_EQ(r->body, expectedBody);
|
||||
EXPECT_EQ(r->get_header_value("Content-Type"), "text/html; charset=utf-8");
|
||||
TEST_F(ServerTest, 500)
|
||||
{
|
||||
struct TestData {
|
||||
const std::string url;
|
||||
const std::string error;
|
||||
};
|
||||
const TestData testData[] = {
|
||||
{
|
||||
"/ROOT%23%3F/content/poor/A/redirect_loop.html",
|
||||
"Entry redirect_loop.html is a redirect entry."
|
||||
},
|
||||
|
||||
{
|
||||
"/ROOT%23%3F/content/poor/A/redirect_loop.html?userlang=test",
|
||||
"Entry redirect_loop.html is a redirect entry."
|
||||
}
|
||||
};
|
||||
|
||||
for ( const auto& td : testData ) {
|
||||
const TestContext ctx{ {"url", td.url} };
|
||||
const auto r = zfs1_->GET(td.url.c_str());
|
||||
EXPECT_EQ(r->status, 500) << ctx;
|
||||
EXPECT_EQ(r->get_header_value("Content-Type"), "text/html; charset=utf-8") << ctx;
|
||||
EXPECT_EQ(r->body, expectedSexy500ErrorHtml(td.url, td.error)) << ctx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1249,12 +1316,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ar",
|
||||
"self_name": "الإنجليزية",
|
||||
"translation_count": 43
|
||||
"translation_count": 44
|
||||
},
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 24
|
||||
"translation_count": 34
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
@@ -1284,7 +1351,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "en",
|
||||
"self_name": "English",
|
||||
"translation_count": 77
|
||||
"translation_count": 93
|
||||
},
|
||||
{
|
||||
"iso_code": "es",
|
||||
@@ -1299,7 +1366,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 68
|
||||
"translation_count": 84
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
@@ -1309,22 +1376,27 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 69
|
||||
"translation_count": 80
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
"self_name": "हिन्दी",
|
||||
"translation_count": 59
|
||||
},
|
||||
{
|
||||
"iso_code": "hu",
|
||||
"self_name": "Magyar",
|
||||
"translation_count": 32
|
||||
},
|
||||
{
|
||||
"iso_code": "hy",
|
||||
"self_name": "Հայերեն",
|
||||
"translation_count": 25
|
||||
"translation_count": 26
|
||||
},
|
||||
{
|
||||
"iso_code": "ia",
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 67
|
||||
"translation_count": 84
|
||||
},
|
||||
{
|
||||
"iso_code": "id",
|
||||
@@ -1339,7 +1411,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 54
|
||||
"translation_count": 58
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -1349,7 +1421,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ko",
|
||||
"self_name": "한국어",
|
||||
"translation_count": 64
|
||||
"translation_count": 73
|
||||
},
|
||||
{
|
||||
"iso_code": "ku-latn",
|
||||
@@ -1359,12 +1431,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "lb",
|
||||
"self_name": "Lëtzebuergesch",
|
||||
"translation_count": 43
|
||||
"translation_count": 48
|
||||
},
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 76
|
||||
"translation_count": 81
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
@@ -1374,7 +1446,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "nb",
|
||||
"self_name": "Engelsk",
|
||||
"translation_count": 50
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "nl",
|
||||
@@ -1399,7 +1471,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "pt-br",
|
||||
"self_name": "Português",
|
||||
"translation_count": 65
|
||||
"translation_count": 66
|
||||
},
|
||||
{
|
||||
"iso_code": "pt",
|
||||
@@ -1409,7 +1481,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ro",
|
||||
"self_name": "Engleză",
|
||||
"translation_count": 67
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
@@ -1429,7 +1501,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "skr-arab",
|
||||
"self_name": "سرائیکی",
|
||||
"translation_count": 31
|
||||
"translation_count": 33
|
||||
},
|
||||
{
|
||||
"iso_code": "sl",
|
||||
@@ -1469,7 +1541,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "zh-hant",
|
||||
"self_name": "繁體中文",
|
||||
"translation_count": 76
|
||||
"translation_count": 92
|
||||
}
|
||||
])EXPECTEDRESPONSE");
|
||||
}
|
||||
|
||||
@@ -758,8 +758,8 @@ struct TestData
|
||||
? R"(No results were found for <b>"PATTERN"</b>)"
|
||||
: R"(Results <b>FIRSTRESULT-LASTRESULT</b> of <b>RESULTCOUNT</b> for <b>"PATTERN"</b>)";
|
||||
|
||||
const size_t lastResultIndex = std::min(totalResultCount, firstResultIndex + results.size() - 1);
|
||||
header = replace(header, "FIRSTRESULT", std::to_string(firstResultIndex));
|
||||
const size_t lastResultIndex = std::min(totalResultCount, firstResultIndex + results.size());
|
||||
header = replace(header, "FIRSTRESULT", std::to_string(firstResultIndex+1));
|
||||
header = replace(header, "LASTRESULT", std::to_string(lastResultIndex));
|
||||
header = replace(header, "RESULTCOUNT", std::to_string(totalResultCount));
|
||||
header = replace(header, "PATTERN", getPattern());
|
||||
@@ -940,7 +940,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 0,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {},
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -951,7 +951,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 0,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {},
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -961,7 +961,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/We_Gonna_Move_to_the_Outskirts_of_Town",
|
||||
@@ -979,7 +979,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
@@ -1005,7 +1005,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
@@ -1028,10 +1028,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=yellow&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 1,
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
@@ -1057,7 +1057,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 100,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ LARGE_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -1068,7 +1068,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 100,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ LARGE_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -1078,7 +1078,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[0],
|
||||
LARGE_SEARCH_RESULTS[1],
|
||||
@@ -1099,10 +1099,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 6,
|
||||
/* start */ 5,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 6,
|
||||
/* firstResultIndex */ 5,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[5],
|
||||
LARGE_SEARCH_RESULTS[6],
|
||||
@@ -1124,10 +1124,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 11,
|
||||
/* start */ 10,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 11,
|
||||
/* firstResultIndex */ 10,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[10],
|
||||
LARGE_SEARCH_RESULTS[11],
|
||||
@@ -1150,10 +1150,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 16,
|
||||
/* start */ 15,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 16,
|
||||
/* firstResultIndex */ 15,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[15],
|
||||
LARGE_SEARCH_RESULTS[16],
|
||||
@@ -1177,10 +1177,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 21,
|
||||
/* start */ 20,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 21,
|
||||
/* firstResultIndex */ 20,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[20],
|
||||
LARGE_SEARCH_RESULTS[21],
|
||||
@@ -1204,10 +1204,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 26,
|
||||
/* start */ 25,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 26,
|
||||
/* firstResultIndex */ 25,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[25],
|
||||
LARGE_SEARCH_RESULTS[26],
|
||||
@@ -1231,10 +1231,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 31,
|
||||
/* start */ 30,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 31,
|
||||
/* firstResultIndex */ 30,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[30],
|
||||
LARGE_SEARCH_RESULTS[31],
|
||||
@@ -1257,10 +1257,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 36,
|
||||
/* start */ 35,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 36,
|
||||
/* firstResultIndex */ 35,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[35],
|
||||
LARGE_SEARCH_RESULTS[36],
|
||||
@@ -1282,10 +1282,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 41,
|
||||
/* start */ 40,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 41,
|
||||
/* firstResultIndex */ 40,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[40],
|
||||
LARGE_SEARCH_RESULTS[41],
|
||||
@@ -1305,10 +1305,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 22,
|
||||
/* start */ 21,
|
||||
/* resultsPerPage */ 3,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 22,
|
||||
/* firstResultIndex */ 21,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[21],
|
||||
LARGE_SEARCH_RESULTS[22],
|
||||
@@ -1334,10 +1334,10 @@ TEST(ServerSearchTest, searchResults)
|
||||
// works, not how it should work!
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 46,
|
||||
/* start */ 45,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 46,
|
||||
/* firstResultIndex */ 45,
|
||||
/* results */ {},
|
||||
|
||||
/* pagination */ {
|
||||
@@ -1357,7 +1357,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM,
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
@@ -1371,7 +1371,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM,
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
@@ -1386,7 +1386,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
|
||||
},
|
||||
@@ -1401,7 +1401,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
},
|
||||
@@ -1416,7 +1416,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
|
||||
},
|
||||
@@ -1431,7 +1431,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
},
|
||||
@@ -1447,7 +1447,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 100,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ LARGE_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -1458,7 +1458,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[0],
|
||||
LARGE_SEARCH_RESULTS[1],
|
||||
@@ -1483,7 +1483,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[0],
|
||||
LARGE_SEARCH_RESULTS[1],
|
||||
@@ -1509,7 +1509,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[0],
|
||||
LARGE_SEARCH_RESULTS[1],
|
||||
@@ -1536,7 +1536,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 5,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
LARGE_SEARCH_RESULTS[0],
|
||||
LARGE_SEARCH_RESULTS[1],
|
||||
|
||||
@@ -62,6 +62,7 @@ public: // types
|
||||
WITH_LIBRARY_BUTTON = 1 << 2,
|
||||
BLOCK_EXTERNAL_LINKS = 1 << 3,
|
||||
NO_NAME_MAPPER = 1 << 4,
|
||||
CATALOG_ONLY_MODE = 1 << 5,
|
||||
|
||||
WITH_TASKBAR_AND_LIBRARY_BUTTON = WITH_TASKBAR | WITH_LIBRARY_BUTTON,
|
||||
|
||||
@@ -71,6 +72,7 @@ public: // types
|
||||
struct Cfg
|
||||
{
|
||||
std::string root = "ROOT#?";
|
||||
std::string contentServerUrl = "";
|
||||
Options options = DEFAULT_OPTIONS;
|
||||
|
||||
Cfg(Options opts = DEFAULT_OPTIONS) : options(opts) {}
|
||||
@@ -149,6 +151,8 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
|
||||
server->setTaskbar(cfg.options & WITH_TASKBAR, cfg.options & WITH_LIBRARY_BUTTON);
|
||||
server->setBlockExternalLinks(cfg.options & BLOCK_EXTERNAL_LINKS);
|
||||
server->setMultiZimSearchLimit(3);
|
||||
server->setCatalogOnlyMode(cfg.options & CATALOG_ONLY_MODE);
|
||||
server->setContentServerUrl(cfg.contentServerUrl);
|
||||
if (!indexTemplateString.empty()) {
|
||||
server->setIndexTemplateString(indexTemplateString);
|
||||
}
|
||||
|
||||
217
test/spelling_correction.cpp
Normal file
217
test/spelling_correction.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Veloman Yunkan
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
|
||||
* NON-INFRINGEMENT. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "../include/spelling_correction.h"
|
||||
#include "../src/tools/pathTools.h"
|
||||
#include "zim/archive.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <xapian.h>
|
||||
|
||||
const std::string TEST_DB_PATH = "./spellings.db";
|
||||
|
||||
class SpellingCorrectionTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override {
|
||||
tmpDirPath = makeTmpDirectory();
|
||||
archive = std::make_unique<zim::Archive>("./test/spelling_correction_test.zim");
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
std::filesystem::remove_all(tmpDirPath);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::filesystem::path tmpDirPath;
|
||||
std::unique_ptr<zim::Archive> archive;
|
||||
};
|
||||
|
||||
void testSpellingCorrections(const kiwix::SpellingsDB& spellingsDB)
|
||||
{
|
||||
#define EXPECT_SPELLING_CORRECTION(query, maxSuggestions, parenthesizedExpectedResult) \
|
||||
EXPECT_EQ( \
|
||||
spellingsDB.getSpellingCorrections(query, maxSuggestions), \
|
||||
std::vector<std::string> parenthesizedExpectedResult \
|
||||
)
|
||||
|
||||
EXPECT_SPELLING_CORRECTION("", 1, ({}));
|
||||
|
||||
EXPECT_SPELLING_CORRECTION("geflekt", 1, ({"gefleckt"}));
|
||||
EXPECT_SPELLING_CORRECTION("Teler", 1, ({"Teller"}));
|
||||
EXPECT_SPELLING_CORRECTION("Teler", 1, ({"Teller"}));
|
||||
EXPECT_SPELLING_CORRECTION("kämen", 1, ({"kämmen"}));
|
||||
EXPECT_SPELLING_CORRECTION("abonieren", 1, ({"abonnieren"}));
|
||||
EXPECT_SPELLING_CORRECTION("abbonnieren", 1, ({"abonnieren"}));
|
||||
EXPECT_SPELLING_CORRECTION("abbonieren", 1, ({"abonnieren"}));
|
||||
EXPECT_SPELLING_CORRECTION("Aplaus", 1, ({"Applaus"}));
|
||||
EXPECT_SPELLING_CORRECTION("konkurieren", 1, ({"konkurrieren"}));
|
||||
EXPECT_SPELLING_CORRECTION("Asisstent", 1, ({"Assistent"}));
|
||||
EXPECT_SPELLING_CORRECTION("Assisstent", 1, ({"Assistent"}));
|
||||
EXPECT_SPELLING_CORRECTION("Atacke", 1, ({"Attacke"}));
|
||||
EXPECT_SPELLING_CORRECTION("atestieren", 1, ({"attestieren"}));
|
||||
EXPECT_SPELLING_CORRECTION("entäuschen", 1, ({"enttäuschen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Enzündung", 1, ({"Entzündung"}));
|
||||
EXPECT_SPELLING_CORRECTION("Schirmütze", 1, ({"Schirmmütze"}));
|
||||
EXPECT_SPELLING_CORRECTION("Termoskanne", 1, ({"Thermoskanne"}));
|
||||
EXPECT_SPELLING_CORRECTION("Tsunge", 1, ({"Zunge"}));
|
||||
EXPECT_SPELLING_CORRECTION("vort", 1, ({"fort"}));
|
||||
EXPECT_SPELLING_CORRECTION("Schtuhl", 1, ({"Stuhl"}));
|
||||
EXPECT_SPELLING_CORRECTION("beissen", 1, ({"beißen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Camera", 1, ({"Kamera"}));
|
||||
EXPECT_SPELLING_CORRECTION("Kaos", 1, ({"Chaos"}));
|
||||
|
||||
// The spelling correction "Lax -> Lachs" is affected by commit
|
||||
// https://github.com/xapian/xapian/commit/0cbe35de5c392623388946e6769aa03f912fdde4
|
||||
// which caps the edit distance at (length(query_word) - 1). As a result, the
|
||||
// max edit distance parameter that we pass into get_spelling_suggestion() is
|
||||
// reduced from 3 to 2 and is below the edit distance of "Lachs" from "Lax".
|
||||
const auto xapianVersion = std::make_tuple(Xapian::major_version(),
|
||||
Xapian::minor_version(),
|
||||
Xapian::revision());
|
||||
if ( xapianVersion < std::make_tuple(1, 4, 19) ) {
|
||||
EXPECT_SPELLING_CORRECTION("Lax", 1, ({"Lachs"}));
|
||||
} else {
|
||||
EXPECT_SPELLING_CORRECTION("Lax", 1, ({}));
|
||||
}
|
||||
|
||||
EXPECT_SPELLING_CORRECTION("Mont", 1, ({"Mond"}));
|
||||
EXPECT_SPELLING_CORRECTION("Umweltstandart", 1, ({"Umweltstandard"}));
|
||||
EXPECT_SPELLING_CORRECTION("seid", 1, ({"seit"}));
|
||||
EXPECT_SPELLING_CORRECTION("Trok", 1, ({"Trog"}));
|
||||
EXPECT_SPELLING_CORRECTION("Unfuk", 1, ({"Unfug"}));
|
||||
EXPECT_SPELLING_CORRECTION("schupsen", 1, ({"schubsen"}));
|
||||
EXPECT_SPELLING_CORRECTION("warscheinlich", 1, ({"wahrscheinlich"}));
|
||||
EXPECT_SPELLING_CORRECTION("gefärlich", 1, ({"gefährlich"}));
|
||||
EXPECT_SPELLING_CORRECTION("Son", 1, ({"Sohn"}));
|
||||
EXPECT_SPELLING_CORRECTION("nähmlich", 1, ({"nämlich"}));
|
||||
EXPECT_SPELLING_CORRECTION("Grahl", 1, ({"Gral"}));
|
||||
EXPECT_SPELLING_CORRECTION("Bine", 1, ({"Biene"}));
|
||||
EXPECT_SPELLING_CORRECTION("Hirarchie", 1, ({"Hierarchie"}));
|
||||
EXPECT_SPELLING_CORRECTION("Priese", 1, ({"Prise"}));
|
||||
EXPECT_SPELLING_CORRECTION("auslehren", 1, ({"ausleeren"}));
|
||||
EXPECT_SPELLING_CORRECTION("Phenomen", 1, ({"Phänomen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Phänomän", 1, ({"Phänomen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Phenomän", 1, ({"Phänomen"}));
|
||||
EXPECT_SPELLING_CORRECTION("gewehren", 1, ({"gewähren"}));
|
||||
EXPECT_SPELLING_CORRECTION("aba", 1, ({"aber"}));
|
||||
EXPECT_SPELLING_CORRECTION("gestan", 1, ({"gestern"}));
|
||||
EXPECT_SPELLING_CORRECTION("ronterfallen", 1, ({"runterfallen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Hönig", 1, ({"Honig"}));
|
||||
EXPECT_SPELLING_CORRECTION("mussen", 1, ({"müssen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Bewandnis", 1, ({"Bewandtnis"}));
|
||||
EXPECT_SPELLING_CORRECTION("hässlig", 1, ({"hässlich"}));
|
||||
EXPECT_SPELLING_CORRECTION("lustich", 1, ({"lustig"}));
|
||||
EXPECT_SPELLING_CORRECTION("Botschaftler", 1, ({"Botschafter"}));
|
||||
EXPECT_SPELLING_CORRECTION("ebemfalls", 1, ({"ebenfalls"}));
|
||||
EXPECT_SPELLING_CORRECTION("samft", 1, ({"sanft"}));
|
||||
EXPECT_SPELLING_CORRECTION("Wohenzimmer", 1, ({"Wohnzimmer"}));
|
||||
EXPECT_SPELLING_CORRECTION("Flaster", 1, ({"Pflaster"}));
|
||||
EXPECT_SPELLING_CORRECTION("Imfung", 1, ({"Impfung"}));
|
||||
EXPECT_SPELLING_CORRECTION("amptieren", 1, ({"amtieren"}));
|
||||
EXPECT_SPELLING_CORRECTION("Endgeld", 1, ({"Entgelt"}));
|
||||
EXPECT_SPELLING_CORRECTION("Abendteuer", 1, ({"Abenteuer"}));
|
||||
EXPECT_SPELLING_CORRECTION("sampft", 1, ({"sanft"}));
|
||||
EXPECT_SPELLING_CORRECTION("forgestan", 1, ({"vorgestern"}));
|
||||
EXPECT_SPELLING_CORRECTION("Füreschein", 1, ({"Führerschein"}));
|
||||
EXPECT_SPELLING_CORRECTION("ronterfalen", 1, ({"runterfallen"}));
|
||||
EXPECT_SPELLING_CORRECTION("Farradschluss", 1, ({"Fahrradschloss"}));
|
||||
EXPECT_SPELLING_CORRECTION("Konkorenz", 1, ({"Konkurrenz"}));
|
||||
EXPECT_SPELLING_CORRECTION("Hirachie", 1, ({"Hierarchie"}));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Edge cases
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Exact match is not considered a spelling correction
|
||||
EXPECT_SPELLING_CORRECTION("Führerschein", 1, ({}));
|
||||
|
||||
// Max edit distance is 3
|
||||
EXPECT_SPELLING_CORRECTION( "Führersch", 1, ({"Führerschein"}));
|
||||
EXPECT_SPELLING_CORRECTION("Führersc", 1, ({}));
|
||||
// Case matters in edit distance
|
||||
EXPECT_SPELLING_CORRECTION("führersch", 1, ({}));
|
||||
// Diacritics matters in edit distance
|
||||
EXPECT_SPELLING_CORRECTION("Fuhrersch", 1, ({}));
|
||||
// Mismatch in diacritics counts as 1 in edit distance (this is not trivial,
|
||||
// because from the UTF-8 perspective it is a one-byte vs two-byte encoding
|
||||
// of a Unicode codepoint).
|
||||
EXPECT_SPELLING_CORRECTION("Führersche", 1, ({"Führerschein"}));
|
||||
|
||||
EXPECT_SPELLING_CORRECTION("Führershine", 1, ({"Führerschein"}));
|
||||
EXPECT_SPELLING_CORRECTION("Führershyne", 1, ({}));
|
||||
EXPECT_SPELLING_CORRECTION("führershine", 1, ({}));
|
||||
|
||||
EXPECT_SPELLING_CORRECTION("Führerschrom", 1, ({"Führerschein"}));
|
||||
EXPECT_SPELLING_CORRECTION("Führerscdrom", 1, ({}));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Shortcomings of the proof-of-concept implementation
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Multiword titles are treated as a single entity
|
||||
EXPECT_SPELLING_CORRECTION("Laurem", 1, ({}));
|
||||
EXPECT_SPELLING_CORRECTION("ibsum", 1, ({}));
|
||||
EXPECT_SPELLING_CORRECTION("Loremipsum", 1, ({"Lorem ipsum"}));
|
||||
|
||||
// Only one spelling correction can be requested
|
||||
// EXPECT_SPELLING_CORRECTION("Kung", 2, ({"King", "Kong"}));
|
||||
EXPECT_THROW(spellingsDB.getSpellingCorrections("Kung", 2), std::runtime_error);
|
||||
}
|
||||
|
||||
using StrCollection = std::vector<std::string>;
|
||||
|
||||
StrCollection directoryEntries(std::filesystem::path dirPath)
|
||||
{
|
||||
StrCollection result;
|
||||
for ( const auto& dirEntry : std::filesystem::directory_iterator(dirPath) ) {
|
||||
result.push_back(dirEntry.path().string());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_F(SpellingCorrectionTest, allInOne)
|
||||
{
|
||||
const auto tmpDirModTime0 = std::filesystem::last_write_time(tmpDirPath);
|
||||
ASSERT_TRUE(directoryEntries(tmpDirPath).empty());
|
||||
{
|
||||
const kiwix::SpellingsDB spellingsDB(*archive, tmpDirPath);
|
||||
testSpellingCorrections(spellingsDB);
|
||||
}
|
||||
|
||||
const auto tmpDirModTime1 = std::filesystem::last_write_time(tmpDirPath);
|
||||
|
||||
const auto spellingsDbPath = tmpDirPath / "554c9707-897e-097a-53ba-1b1306d8bb88.spellingsdb.v0.1";
|
||||
|
||||
const StrCollection EXPECTED_DIR_CONTENT{ spellingsDbPath.string() };
|
||||
ASSERT_EQ(directoryEntries(tmpDirPath), EXPECTED_DIR_CONTENT);
|
||||
ASSERT_LT(tmpDirModTime0, tmpDirModTime1);
|
||||
const auto fileModTime = std::filesystem::last_write_time(spellingsDbPath);
|
||||
|
||||
{
|
||||
const kiwix::SpellingsDB spellingsDB(*archive, tmpDirPath);
|
||||
testSpellingCorrections(spellingsDB);
|
||||
}
|
||||
|
||||
ASSERT_EQ(directoryEntries(tmpDirPath), EXPECTED_DIR_CONTENT );
|
||||
ASSERT_EQ(tmpDirModTime1, std::filesystem::last_write_time(tmpDirPath));
|
||||
ASSERT_EQ(fileModTime, std::filesystem::last_write_time(spellingsDbPath));
|
||||
}
|
||||
Reference in New Issue
Block a user