Compare commits

..

4 Commits

Author SHA1 Message Date
sgourdas
528a532b27 fixup! Get ipv4 working on dual stack 2024-10-06 13:24:52 +03:00
sgourdas
fa410f739c Fix Server::setAddress 2024-10-05 15:24:21 +03:00
sgourdas
5f1f37bd2a kiwix-serve: Error message reword 2024-10-05 12:04:45 +03:00
sgourdas
5855791899 kiwix-serve: Disable IPV6_ONLY on Windows 2024-10-05 12:01:40 +03:00
98 changed files with 632 additions and 2966 deletions

View File

@@ -11,36 +11,39 @@ 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: false
run_test: true
- target: ios-x86_64-dyn
arch_name: x86-apple-ios-simulator
run_test: false
runs-on: macos-15
run_test: true
runs-on: ${{ matrix.os }}
env:
HOME: /Users/runner
steps:
- name: Retrieve source code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install packages
run: |
brew update
brew install ninja meson
brew unlink python3
# upgrade from python@3.12 to python@3.12.2 fails to overwrite those
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.12 /usr/local/bin/idle3 /usr/local/bin/idle3.12 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.12 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.12 /usr/local/bin/python3.12-config
brew install pkg-config ninja meson
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
- name: Install dependencies
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
@@ -102,7 +105,7 @@ jobs:
run: |
set PKG_CONFIG_PATH=%cd%\BUILD_win-amd64\INSTALL\lib\pkgconfig
set CPPFLAGS=-I%cd%\BUILD_win-amd64\INSTALL\include
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=release
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=debug
cd build
ninja.exe
@@ -123,36 +126,39 @@ 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
coverage: false
env:
HOME: /home/runner
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
container:
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:2025-06-07"
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:38"
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install dependencies
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
with:

View File

@@ -21,6 +21,7 @@ jobs:
# - debian-bullseye
- ubuntu-noble
- ubuntu-jammy
- ubuntu-focal
steps:
- uses: actions/checkout@v4
@@ -31,9 +32,9 @@ jobs:
run: |
if [[ $REF == refs/tags* ]]
then
echo "ppa=kiwixteam/release" >> $GITHUB_OUTPUT
echo "::set-output name=ppa::kiwixteam/release"
else
echo "ppa=kiwixteam/dev" >> $GITHUB_OUTPUT
echo "::set-output name=ppa::kiwixteam/dev"
fi
env:
REF: ${{ github.ref }}
@@ -88,7 +89,15 @@ jobs:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: actions/upload-artifact@v4
- 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@v3
with:
name: Packages for ${{ matrix.distro }}
path: output

View File

@@ -1,31 +1,3 @@
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
===============

View File

@@ -7,7 +7,7 @@ GNU/Linux, macOS, Android, iOS, ...).
[![Release](https://img.shields.io/github/v/tag/kiwix/libkiwix?label=release&sort=semver)](https://download.kiwix.org/release/libkiwix/)
[![Repositories](https://img.shields.io/repology/repositories/libkiwix?label=repositories)](https://github.com/kiwix/libkiwix/wiki/Repology)
[![Build Status](https://github.com/kiwix/libkiwix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kiwix/libkiwix/actions?query=branch%3Amain)
[![Build Status](https://github.com/kiwix/libkiwix/workflows/CI/badge.svg?query=branch%3Amain)](https://github.com/kiwix/libkiwix/actions?query=branch%3Amain)
[![Doc](https://readthedocs.org/projects/libkiwix/badge/?style=flat)](https://libkiwix.readthedocs.org/en/latest/?badge=latest)
[![CodeFactor](https://www.codefactor.io/repository/github/kiwix/libkiwix/badge)](https://www.codefactor.io/repository/github/kiwix/libkiwix)
[![Codecov](https://codecov.io/gh/kiwix/libkiwix/branch/main/graph/badge.svg)](https://codecov.io/gh/kiwix/libkiwix)

4
debian/control vendored
View File

@@ -4,7 +4,7 @@ Maintainer: Kiwix team <kiwix@kiwix.org>
Build-Depends: debhelper-compat (= 13),
meson,
pkgconf,
libzim-dev (>= 9.0), libzim-dev (<< 10.0),
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
libcurl4-gnutls-dev,
libicu-dev,
libgtest-dev,
@@ -22,7 +22,7 @@ Section: libdevel
Architecture: any
Multi-Arch: same
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
libzim-dev (>= 9.0), libzim-dev (<< 10.0),
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
libicu-dev,
libpugixml-dev,
libcurl4-gnutls-dev,

View File

@@ -155,15 +155,6 @@ 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;

View File

@@ -7,7 +7,6 @@ headers = [
'downloader.h',
'search_renderer.h',
'server.h',
'spelling_correction.h',
'kiwixserve.h',
'name_mapper.h',
'tools.h',

View File

@@ -50,7 +50,7 @@ class HumanReadableNameMapper : public NameMapper {
std::map<std::string, std::string> m_nameToId;
public:
HumanReadableNameMapper(const kiwix::Library& library, bool withAlias);
HumanReadableNameMapper(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;

View File

@@ -63,13 +63,10 @@ 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;
@@ -86,8 +83,6 @@ 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;
};
}

View File

@@ -1,58 +0,0 @@
/*
* 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

View File

@@ -1,61 +1,26 @@
project('libkiwix', 'cpp',
version : '14.1.1',
version : '14.0.0',
license : 'GPLv3+',
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
compiler = meson.get_compiler('cpp')
static_deps = get_option('static-linkage') or get_option('default_library') == 'static'
extra_libs = []
# Atomics as compiled by GCC or clang can lead to external references to
# functions depending on the type size and the platform. LLVM provides them in
# 'libcompiler_rt', which clang normally automatically links in, while GNU
# provides them in 'libatomic', which GCC *does not* link in automatically (but
# this is probably going to change, see
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81358). Regardless of the setup
# of the compiler driver itself (GCC or clang), we can thus assume that if some
# atomic references can't be resolved, then 'libatomic' is missing.
atomics_program = '''
#include <atomic>
#include <cstdint>
using namespace std;
int main() {
volatile atomic_bool a_b(true);
volatile atomic_ullong a_ull(-1);
// Next two lines are to cover atomic<socket_t> from 'httplib.h'.
volatile atomic<uint32_t> a_u32(-1);
volatile atomic<uint64_t> a_u64(-1);
return atomic_load(&a_b) == false && atomic_load(&a_ull) == 0 &&
atomic_load(&a_u32) == 0 && atomic_load(&a_u64) == 0;
}
'''
if not compiler.links(atomics_program,
name: 'compiler driver readily supports atomics')
libatomic = compiler.find_library('atomic')
compiler.links(atomics_program, name: 'atomics work with libatomic',
dependencies: libatomic, required: true)
extra_libs += ['-latomic']
# See https://github.com/kiwix/libkiwix/issues/371
if ['arm', 'mips', 'm68k', 'ppc', 'sh4'].contains(host_machine.cpu_family())
extra_libs = ['-latomic']
else
extra_libs = []
endif
# C++ std::thread is implemented using pthread on Linux by GCC, and on FreeBSD
# for both GCC and LLVM.
if (host_machine.system() == 'linux' and compiler.get_id() == 'gcc') or \
host_machine.system() == 'freebsd'
if (compiler.get_id() == 'gcc' and build_machine.system() == 'linux') or host_machine.system() == 'freebsd'
# C++ std::thread is implemented using pthread on linux by gcc
thread_dep = dependency('threads')
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)
@@ -70,7 +35,7 @@ else
error('Cannot found header mustache.hpp')
endif
libzim_dep = dependency('libzim', version:['>=9.4.0', '<10.0.0'], static:static_deps)
libzim_dep = dependency('libzim', version:['>=9.0.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.')
@@ -93,11 +58,7 @@ if build_machine.system() == 'windows'
endif
# 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
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
inc = include_directories('include', extra_include)

View File

@@ -124,7 +124,7 @@ Aria2::Aria2(std::string sessionFileDir):
typedef std::chrono::duration<double> Seconds;
const double MAX_WAITING_TIME_SECONDS = 1;
const double MAX_WAITING_TIME_SECONDS = 0.5;
const auto t0 = std::chrono::steady_clock::now();
bool maxWaitingTimeWasExceeded = false;

View File

@@ -82,11 +82,10 @@ void Book::update(const zim::Archive& archive) {
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
m_illustrations.clear();
for ( const auto& illustrationInfo : archive.getIllustrationInfos() ) {
for ( const auto illustrationSize : archive.getIllustrationSizes() ) {
const auto illustration = std::make_shared<Illustration>();
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationInfo);
illustration->width = illustrationInfo.width;
illustration->height = illustrationInfo.height;
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationSize);
illustration->width = illustration->height = illustrationSize;
illustration->mimeType = illustrationItem.getMimetype();
illustration->data = illustrationItem.getData();
// NOTE: illustration->url is left uninitialized

View File

@@ -27,30 +27,6 @@ std::string humanFriendlyTitle(std::string title)
return humanFriendlyString;
}
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;
langShortString = "mul";
for (const auto& lang : bookLanguages) {
const std::string fullLang = getLanguageSelfName(lang);
mulLanguages.push_back(fullLang);
}
langFullString = kiwix::join(mulLanguages, ",");
} else if(bookLanguages.size() == 1) {
langShortString = bookLanguages[0];
langFullString = getLanguageSelfName(langShortString);
}
kainjow::mustache::object langTag;
langTag["langShortString"] = langShortString;
langTag["langFullString"] = langFullString;
return langTag;
}
kainjow::mustache::list getTagList(std::string tags)
{
const auto tagsList = kiwix::split(tags, ";", true, false);
@@ -96,16 +72,17 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
contentId = urlEncode(nameMapper->getNameForId(bookId));
} catch (...) {}
const auto bookDescription = bookObj.getDescription();
const auto langCode = bookObj.getCommaSeparatedLanguages();
const auto bookIconUrl = rootLocation + "/catalog/v2/illustration/" + bookId + "/?size=48";
const auto tags = bookObj.getTags();
const auto downloadAvailable = (bookObj.getUrl() != "");
const auto langTagObj = getLangTag(bookObj.getLanguages());
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
booksData.push_back(kainjow::mustache::object{
{"id", contentId},
{"title", bookTitle},
{"description", bookDescription},
{"langTag", langTagObj},
{"langCode", langCode},
{"faviconAttr", faviconAttr},
{"tagList", getTagList(tags)},
{"downloadAvailable", downloadAvailable}
@@ -130,7 +107,6 @@ 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},

View File

@@ -50,13 +50,6 @@ 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.
*
@@ -65,10 +58,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; }
@@ -88,7 +81,6 @@ 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;

View File

@@ -23,14 +23,6 @@
#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
{
@@ -259,58 +251,6 @@ 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;

View File

@@ -31,7 +31,6 @@ kiwix_sources = [
'server/internalServer_catalog.cpp',
'server/i18n.cpp',
'opds_catalog.cpp',
'spelling_correction.cpp',
'version.cpp'
]
kiwix_sources += lib_resources

View File

@@ -24,8 +24,8 @@
namespace kiwix {
HumanReadableNameMapper::HumanReadableNameMapper(const kiwix::Library& library, bool withAlias) {
for (auto& bookId: library.filter(kiwix::Filter())) {
HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool withAlias) {
for (auto& bookId: library.filter(kiwix::Filter().local(true).valid(true))) {
auto& currentBook = library.getBookById(bookId);
auto bookName = currentBook.getHumanReadableIdFromPath();
m_idToName[bookId] = bookName;

View File

@@ -51,26 +51,24 @@ typedef kainjow::mustache::list IllustrationInfo;
IllustrationInfo getBookIllustrationInfo(const Book& book)
{
kainjow::mustache::list illustrations;
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}
});
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}
});
}
}
return illustrations;
}
std::string fullEntryXML(const Book& book,
const std::string& rootLocation,
const std::string& contentAccessUrl,
const std::string& contentId)
std::string fullEntryXML(const Book& book, const std::string& rootLocation, 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()},
@@ -107,12 +105,7 @@ 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,
const std::string& contentAccessUrl,
bool partial)
BooksData getBooksData(const Library* library, const NameMapper* nameMapper, const std::vector<std::string>& bookIds, const std::string& rootLocation, bool partial)
{
BooksData booksData;
for ( const auto& bookId : bookIds ) {
@@ -121,7 +114,7 @@ BooksData getBooksData(const Library* library,
const std::string contentId = nameMapper->getNameForId(bookId);
const auto entryXML = partial
? partialEntryXML(book, rootLocation)
: fullEntryXML(book, rootLocation, contentAccessUrl, contentId);
: fullEntryXML(book, rootLocation, 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
@@ -136,7 +129,7 @@ BooksData getBooksData(const Library* library,
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
{
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentAccessUrl, false);
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, false);
const kainjow::mustache::object template_data{
{"date", gen_date_str()},
{"root", rootLocation},
@@ -154,7 +147,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, contentAccessUrl, partial);
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, partial);
const char* const endpoint = partial ? "/partial_entries" : "/entries";
const std::string url = endpoint + (query.empty() ? "" : "?" + query);
@@ -179,7 +172,7 @@ std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
const std::string contentId = nameMapper->getNameForId(bookId);
return XML_HEADER
+ "\n"
+ fullEntryXML(book, rootLocation, contentAccessUrl, contentId);
+ fullEntryXML(book, rootLocation, contentId);
}
std::string OPDSDumper::categoriesOPDSFeed() const

View File

@@ -58,7 +58,7 @@ ParameterizedMessage searchResultsPageHeaderMsg(const std::string& searchPattern
return ParameterizedMessage("search-results-page-header",
{
{"SEARCH_PATTERN", searchPattern},
{"START", r.get("startLabel")->string_value()},
{"START", r.get("start")->string_value()},
{"END", r.get("end") ->string_value()},
{"COUNT", r.get("count")->string_value()},
}
@@ -225,8 +225,7 @@ 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("startLabel", kiwix::beautifyInteger(resultStart+1));
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength, estimatedResultCount)));
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength-1, estimatedResultCount)));
// pagination
auto pagination = buildPagination(

View File

@@ -29,22 +29,6 @@
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),
@@ -69,16 +53,8 @@ bool Server::start() {
m_blockExternalLinks,
m_ipMode,
m_indexTemplateString,
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;
}
m_ipConnectionLimit));
return mp_server->start();
}
void Server::stop() {
@@ -91,12 +67,12 @@ void Server::stop() {
void Server::setRoot(const std::string& root)
{
m_root = root;
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;
if (m_root[0] != '/') {
m_root = "/" + m_root;
}
if (m_root.back() == '/') {
m_root.erase(m_root.size() - 1);
}
}
void Server::setAddress(const std::string& addr)
@@ -115,12 +91,12 @@ void Server::setAddress(const std::string& addr)
int Server::getPort() const
{
return m_port;
return mp_server->getPort();
}
IpAddress Server::getAddress() const
{
return m_addr;
return mp_server->getAddress();
}
IpMode Server::getIpMode() const
@@ -128,16 +104,4 @@ 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;
}
}

View File

@@ -99,6 +99,16 @@ 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)
{
@@ -115,12 +125,9 @@ std::string getSearchComponent(const RequestContext& request)
return query.empty() ? query : "?" + query;
}
Filter get_search_filter(const RequestContext& request, const std::string& prefix="", bool catalogOnlyMode = false)
Filter get_search_filter(const RequestContext& request, const std::string& prefix="")
{
auto filter = kiwix::Filter();
if ( !catalogOnlyMode ) {
filter.valid(true).local(true);
}
auto filter = kiwix::Filter().valid(true).local(true);
try {
filter.query(request.get_argument(prefix+"q"));
} catch (const std::out_of_range&) {}
@@ -425,12 +432,10 @@ InternalServer::InternalServer(LibraryPtr library,
bool blockExternalLinks,
IpMode ipMode,
std::string indexTemplateString,
int ipConnectionLimit,
bool catalogOnlyMode,
std::string contentServerUrl) :
int ipConnectionLimit) :
m_addr(addr),
m_port(port),
m_root(root),
m_root(normalizeRootUrl(root)),
m_rootPrefixOfDecodedURL(m_root),
m_nbThreads(nbThreads),
m_multizimSearchLimit(multizimSearchLimit),
@@ -446,9 +451,7 @@ 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_catalogOnlyMode(catalogOnlyMode),
m_contentServerUrl(contentServerUrl)
m_customizedResources(new CustomizedResources)
{
m_root = urlEncode(m_root);
}
@@ -512,7 +515,28 @@ bool InternalServer::start() {
struct sockaddr* sockaddr = (m_ipMode==IpMode::ALL || m_ipMode==IpMode::IPV6)
? (struct sockaddr*)&sockAddr6
: (struct sockaddr*)&sockAddr4;
#ifdef _WIN32
SOCKET sock = INVALID_SOCKET;
if (m_ipMode == IpMode::ALL || m_ipMode == IpMode::IPV6) {
if ((sock = socket(AF_INET6, SOCK_STREAM, 0)) == INVALID_SOCKET) {
std::cerr << "ERROR: Failed to create IPv6 socket" << std::endl;
return false;
}
int opt = 0;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&opt, sizeof(opt)) != 0) {
std::cerr << "ERROR: Failed to set IPV6_V6ONLY option" << std::endl;
closesocket(sock);
return false;
}
if (::bind(sock, (struct sockaddr*)&sockAddr6, sizeof(sockAddr6)) == SOCKET_ERROR) {
std::cerr << "ERROR: Failed to bind IPv6 socket" << std::endl;
closesocket(sock);
return false;
}
}
#endif
mp_daemon = MHD_start_daemon(flags,
m_port,
NULL,
@@ -520,14 +544,21 @@ bool InternalServer::start() {
&staticHandlerCallback,
this,
MHD_OPTION_SOCK_ADDR, sockaddr,
#ifdef _WIN32
(sock == INVALID_SOCKET) ? MHD_OPTION_END : MHD_OPTION_LISTEN_SOCKET, sock,
#endif
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
MHD_OPTION_END);
if (mp_daemon == nullptr) {
std::cerr << "Unable to instantiate the HTTP daemon. The port " << m_port
<< " is maybe already occupied or need more permissions to be open. "
std::cerr << "ERROR: Unable to instantiate the HTTP daemon. The port " << m_port
<< " may already be in use, or more permissions are required to open it. "
"Please try as root or with a port number higher or equal to 1024."
<< std::endl;
#ifdef _WIN32
if (sock != INVALID_SOCKET) closesocket(sock);
#endif
return false;
}
auto server_start_time = std::chrono::system_clock::now().time_since_epoch();
@@ -706,10 +737,12 @@ 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, m_root, request.get_full_url(), e.what());
return HTTP500Response(request)
+ ParameterizedMessage("non-translated-text", {{"MSG", e.what()}});
} catch (...) {
fprintf(stderr, "===== Unhandled unknown error\n");
return HTTP500Response(request, m_root, request.get_full_url());
return HTTP500Response(request)
+ nonParameterizedMessage("unknown-error");
}
}
@@ -840,15 +873,6 @@ 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();
@@ -856,13 +880,12 @@ 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, "", m_catalogOnlyMode);
auto filter = get_search_filter(request);
try {
if (request.get_argument("category") == "") {
filter.clearCategory();
@@ -1011,11 +1034,11 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
return response;
}
const auto start = max(0u, request.get_optional_param("start", 0u));
const auto start = max(1u, request.get_optional_param("start", 1u));
const auto pageLength = getSearchPageSize(request);
/* Get the results */
SearchRenderer renderer(search->getResults(start, pageLength), start,
SearchRenderer renderer(search->getResults(start-1, pageLength), start,
search->getEstimatedMatches());
renderer.setSearchPattern(searchInfo.pattern);
renderer.setSearchBookQuery(searchInfo.bookFilterQuery);
@@ -1090,7 +1113,9 @@ std::unique_ptr<Response> InternalServer::handle_captured_external(const Request
return UrlNotFoundResponse(request);
}
return BlockExternalLinkResponse(request, m_root, source);
auto data = get_default_data();
data.set("source", source);
return ContentResponse::build(RESOURCE::templates::captured_external_html, data, "text/html; charset=utf-8");
}
std::unique_ptr<Response> InternalServer::handle_catch(const RequestContext& request)
@@ -1110,7 +1135,7 @@ std::vector<std::string>
InternalServer::search_catalog(const RequestContext& request,
kiwix::OPDSDumper& opdsDumper)
{
const auto filter = get_search_filter(request, "", m_catalogOnlyMode);
const auto filter = get_search_filter(request);
std::vector<std::string> bookIdsToDump = mp_library->filter(filter);
const auto totalResults = bookIdsToDump.size();
const long count = request.get_optional_param("count", 10L);
@@ -1124,6 +1149,15 @@ InternalServer::search_catalog(const RequestContext& request,
namespace
{
ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::string& pattern)
{
return ParameterizedMessage("suggest-search",
{
{ "PATTERN", pattern },
{ "SEARCH_URL", searchURL }
});
}
///////////////////////////////////////////////////////////////////////////////
// The content security policy below is set on responses to the /content
// endpoint in order to prevent the ZIM content from interfering with the
@@ -1141,7 +1175,6 @@ const std::string CONTENT_CSP_HEADER =
"allow-same-origin "
"allow-modals "
"allow-popups "
"allow-popups-to-escape-sandbox "
"allow-forms "
"allow-downloads;";
@@ -1178,7 +1211,9 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
} catch (const std::out_of_range& e) {}
if (archive == nullptr) {
return NewHTTP404Response(request, m_root, m_root + url);
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern);
return UrlNotFoundResponse(request)
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
}
const std::string archiveUuid(archive->getUuid());
@@ -1223,7 +1258,9 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
if (m_verbose.load())
printf("Failed to find %s\n", urlStr.c_str());
return NewHTTP404Response(request, m_root, m_root + url);
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern);
return UrlNotFoundResponse(request)
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
}
}

View File

@@ -90,7 +90,6 @@ class SearchInfo {
typedef kainjow::mustache::data MustacheData;
class OPDSDumper;
class LibraryDumper;
class InternalServer {
public:
@@ -107,9 +106,7 @@ class InternalServer {
bool blockExternalLinks,
IpMode ipMode,
std::string indexTemplateString,
int ipConnectionLimit,
bool catalogOnlyMode,
std::string zimViewerURL);
int ipConnectionLimit);
virtual ~InternalServer();
MHD_Result handlerCallback(struct MHD_Connection* connection,
@@ -163,8 +160,6 @@ 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;
@@ -197,9 +192,6 @@ class InternalServer {
class CustomizedResources;
std::unique_ptr<CustomizedResources> m_customizedResources;
const bool m_catalogOnlyMode;
const std::string m_contentServerUrl;
};
}

View File

@@ -51,15 +51,6 @@ 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()) {
@@ -89,7 +80,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
std::vector<std::string> bookIdsToDump;
if (url == "root.xml") {
uuid = zim::Uuid::generate(host);
@@ -165,7 +158,9 @@ 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)
{
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
const auto bookIds = search_catalog(request, opdsDumper);
const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial);
return ContentResponse::build(
@@ -182,7 +177,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
return UrlNotFoundResponse(request);
}
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
return ContentResponse::build(
opdsFeed,
@@ -192,7 +189,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
{
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
return ContentResponse::build(
opdsDumper.categoriesOPDSFeed(),
opdsMimeType[OPDS_NAVIGATION_FEED]
@@ -201,7 +200,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
{
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
return ContentResponse::build(
opdsDumper.languagesOPDSFeed(),
opdsMimeType[OPDS_NAVIGATION_FEED]

View File

@@ -243,23 +243,6 @@ public:
};
}
static Data fromMsgId(const std::string& nonParameterizedMsgId)
{
return from(nonParameterizedMessage(nonParameterizedMsgId));
}
static Data staticMultiParagraphText(const std::string& msgIdPrefix, size_t n)
{
Object paragraphs;
for ( size_t i = 1; i <= n; ++i ) {
std::ostringstream oss;
oss << "p" << i;
const std::string pId = oss.str();
paragraphs[pId] = fromMsgId(msgIdPrefix + "." + pId);
}
return paragraphs;
}
std::string asJSON() const;
void dumpJSON(std::ostream& os) const;
@@ -385,45 +368,6 @@ std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObjec
return r;
}
NewHTTP404Response::NewHTTP404Response(const RequestContext& request,
const std::string& root,
const std::string& urlPath)
: ContentResponseBlueprint(&request,
MHD_HTTP_NOT_FOUND,
"text/html; charset=utf-8",
RESOURCE::templates::sexy404_html,
/*includeKiwixResponseData=*/true)
{
*this->m_data = Data(Data::Object{
{"root", root },
{"url_path", urlPath},
{"PAGE_TITLE", Data::fromMsgId("new-404-page-title")},
{"PAGE_HEADING", Data::fromMsgId("new-404-page-heading")},
{"404_img_text", Data::fromMsgId("404-img-text")},
{"path_was_not_found_msg", Data::fromMsgId("path-was-not-found")},
{"advice", Data::staticMultiParagraphText("404-advice", 5)},
});
}
BlockExternalLinkResponse::BlockExternalLinkResponse(const RequestContext& request,
const std::string& root,
const std::string& externalUrl)
: ContentResponseBlueprint(&request,
MHD_HTTP_OK,
"text/html; charset=utf-8",
RESOURCE::templates::captured_external_html,
/*includeKiwixResponseData=*/true)
{
*this->m_data = Data(Data::Object{
{"root", root },
{"external_link_detected", Data::fromMsgId("external-link-detected") },
{"url", externalUrl },
{"caution_warning", Data::fromMsgId("caution-warning") },
{"external_link_intro", Data::fromMsgId("external-link-intro") },
{"advice", Data::staticMultiParagraphText("external-link-advice", 3)},
});
}
HTTPErrorResponse::HTTPErrorResponse(const RequestContext& request,
int httpStatusCode,
const std::string& pageTitleMsgId,
@@ -439,8 +383,8 @@ HTTPErrorResponse::HTTPErrorResponse(const RequestContext& request,
Data::List emptyList;
*this->m_data = Data(Data::Object{
{"CSS_URL", Data::onlyAsNonEmptyValue(cssUrl) },
{"PAGE_TITLE", Data::fromMsgId(pageTitleMsgId)},
{"PAGE_HEADING", Data::fromMsgId(headingMsgId)},
{"PAGE_TITLE", Data::from(nonParameterizedMessage(pageTitleMsgId))},
{"PAGE_HEADING", Data::from(nonParameterizedMessage(headingMsgId))},
{"details", emptyList}
});
}
@@ -491,30 +435,15 @@ HTTP400Response::HTTP400Response(const RequestContext& request)
*this += ParameterizedMessage("invalid-request", {{"url", requestUrl}});
}
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)
HTTP500Response::HTTP500Response(const RequestContext& request)
: HTTPErrorResponse(request,
MHD_HTTP_INTERNAL_SERVER_ERROR,
"500-page-title",
"500-page-heading",
std::string(),
/*includeKiwixResponseData=*/true)
{
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);
*this += nonParameterizedMessage("500-page-text");
}
std::unique_ptr<Response> Response::build_416(size_t resourceLength)

View File

@@ -145,13 +145,6 @@ protected: //data
std::unique_ptr<Data> m_data;
};
struct NewHTTP404Response : ContentResponseBlueprint
{
NewHTTP404Response(const RequestContext& request,
const std::string& root,
const std::string& urlPath);
};
struct HTTPErrorResponse : ContentResponseBlueprint
{
HTTPErrorResponse(const RequestContext& request,
@@ -180,12 +173,9 @@ struct HTTP400Response : HTTPErrorResponse
explicit HTTP400Response(const RequestContext& request);
};
struct HTTP500Response : ContentResponseBlueprint
struct HTTP500Response : HTTPErrorResponse
{
HTTP500Response(const RequestContext& request,
const std::string& root,
const std::string& urlPath,
const std::string& error = "");
explicit HTTP500Response(const RequestContext& request);
};
class ItemResponse : public Response {
@@ -200,13 +190,6 @@ class ItemResponse : public Response {
std::string m_mimeType;
};
struct BlockExternalLinkResponse : ContentResponseBlueprint
{
BlockExternalLinkResponse(const RequestContext& request,
const std::string& root,
const std::string& externalUrl);
};
}
#endif //KIWIXLIB_SERVER_RESPONSE_H

View File

@@ -1,108 +0,0 @@
/*
* 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

View File

@@ -42,7 +42,6 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netdb.h>
#endif
@@ -230,7 +229,7 @@ IpAddress getBestPublicIps() {
}
}
#endif
const char* const v4prefixes[] = { "192.168", "172.16", "10.0" };
const char* const v4prefixes[] = { "192.168", "172.16", "10.0", "169.254" };
for (const auto& prefix : v4prefixes) {
for (const auto& kv : interfaces) {
const auto& interfaceIps = kv.second;

View File

@@ -66,9 +66,6 @@ std::string ucAll(const std::string& word);
std::string lcAll(const std::string& word);
std::string ucFirst(const std::string& word);
std::string lcFirst(const std::string& word);
/* This function is broken, related Github issue
* https://github.com/kiwix/libkiwix/issues/1188 */
std::string toTitle(const std::string& word);
std::string normalize(const std::string& word);

View File

@@ -13,10 +13,8 @@ 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
skin/i18n/ig.json
skin/i18n/it.json
skin/i18n/ja.json
@@ -25,14 +23,11 @@ skin/i18n/ku-latn.json
skin/i18n/lb.json
skin/i18n/mk.json
skin/i18n/ms.json
skin/i18n/nb.json
skin/i18n/nl.json
skin/i18n/nqo.json
skin/i18n/or.json
skin/i18n/pl.json
skin/i18n/pt-br.json
skin/i18n/pt.json
skin/i18n/ro.json
skin/i18n/ru.json
skin/i18n/sc.json
skin/i18n/sk.json

View File

@@ -1,9 +1,6 @@
skin/caret.png
skin/bittorrent.png
skin/magnet.png
skin/404.svg
skin/500.svg
skin/blocklink.svg
skin/feed.svg
skin/langSelector.svg
skin/download.png
@@ -14,12 +11,9 @@ skin/iso6391To3.js
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
skin/fonts/DMSans-Regular.ttf
skin/fonts/Poppins.ttf
skin/fonts/Roboto.ttf
skin/search_results.css
@@ -48,8 +42,6 @@ templates/url_of_search_results_css.tmpl
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

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -23,9 +23,6 @@
.autoComplete_wrapper > ul {
position: absolute;
min-width: 100%;
width: fit-content;
max-width: 600px;
max-height: 226px;
overflow-y: scroll;
top: 100%;
@@ -63,11 +60,6 @@
transition: all 0.2s ease;
}
.autoComplete_wrapper > ul > li > a {
overflow: hidden;
text-overflow: ellipsis;
}
.autoComplete_wrapper > ul > li::selection {
color: rgba(#ffffff, 0);
background-color: rgba(#ffffff, 0);

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 1742.79 1984.21" xmlns="http://www.w3.org/2000/svg"><ellipse cx="667.97" cy="1872.93" fill="#c7c8ca" rx="649.71" ry="111.28"/><path d="m933.76 1775.81c0-29.5-23.92-53.42-53.42-53.42h-163.28c-24.37 0-45.97-15.91-52.98-39.26l-105.96-347.65 18.1-9.63c251.03-105.38 519.58 82.75 706.54-97.93l1.17-1.17c23.21-21.02 46.27-42.47 114.72.29 73.56 46.12 236 166.53 309.71 372.32 0 0 44.66-15.91 32.25-70.49-12.41-54.73-102.89-212.36-287.96-352.18-33.86-25.69-64.36-47-89.76-64.07 36.93-158.21-155.14-349.11-342.69-259.21-134.57-126.54-284.6-178.2-422.67-176.16-365.6 5.11-647.58 386.04-344.3 749.45l.58.58c4.67 5.55 9.49 11.09 14.3 16.64 19.7 23.64 32.69 44.51 43.93 81.29l90.34 296.57h-31.96c-29.48 0-53.42 23.94-53.42 53.42h121.43l204.04 96.47c12.55-26.71 1.17-58.53-25.4-71.08l-53.56-25.25h153.83c0-29.48-23.94-53.42-53.42-53.42h-139.38c-24.37 0-45.97-15.91-52.98-39.26l-71.66-235.42c-17.81-61.88 23.21-102.31 61.74-107.13 35.17-4.38 52.83 18.39 69.47 73.27l63.63 208.85h-31.96c-29.48 0-53.42 23.94-53.42 53.42h121.43l204.04 96.47c12.55-26.71 1.17-58.53-25.4-71.08l-53.56-25.25h177.88z"/><path d="m545.68 2.53h52.19v1441.85h-52.19z" fill="#f89a16" transform="matrix(.9855856 -.16917749 .16917749 .9855856 -114.16 107.17)"/><path d="m581.21 624.21-55.8-17.21-93.34-544.54 53.51 3.6z" fill="#da7c2b"/><path d="m981.62 279.44c-30.85 1.91-83.72 3.58-83.72 3.58l79.01-31.06-43.25-251.96-933.66 160.24 56.63 330 68.11 13.66s-38.47 19-60.89 28.37l22.52 131.23 933.66-160.24z" fill="#f89a16"/><circle cx="1144.16" cy="1031.93" fill="#fff" r="82.26" transform="matrix(.70710678 -.70710678 .70710678 .70710678 -394.57 1111.29)"/><circle cx="1124.15" cy="1004.52" r="52.63" transform="matrix(.41786707 -.90850818 .90850818 .41786707 -258.21 1606.05)"/><g fill="#fff"><path d="m387.43 559.95-69.59-57.58 107.75-360.2 93.69-15.45 226.75 308.17-45.64 75.04-312.97 50.02zm-11.05-75.33 25.78 21.33 266.91-42.65 15.63-25.7-187.97-255.46-31.41 5.18-88.94 297.31z"/><path d="m526.16 300.48 5.44 89.61-20.72 3.32-28.24-85.96-9.08-47.73 43.52-6.98 9.08 47.73zm18.46 103.04 7.02 36.88-41.43 6.64-7.02-36.88z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,159 +0,0 @@
@font-face {
font-family:"DM Sans";
font-style: normal;
font-weight: 400;
src : url('../skin/fonts/DMSans-Regular.ttf?KIWIXCACHEID');
}
@font-face {
font-family:"DM Sans Bold";
font-style: normal;
font-weight: 700;
src : url('../skin/fonts/DMSans-Regular.ttf?KIWIXCACHEID');
}
body {
background: linear-gradient(to bottom right, #ffffff, #e6e6e6);
background-repeat: no-repeat;
background-attachment: fixed;
}
header {
width: 100%;
margin: auto;
text-align: center;
margin-top: 15%;
margin-bottom: 15%;
}
header img {
width: 60%;
min-width: 200px;
max-width: 500px;
max-height: 300px;
}
section {
display: flex;
flex-direction: column;
align-items: center;
}
header, .intro {
font-family: "DM Sans";
}
.intro {
font-size: 1em;
padding: 0 10%;
line-height: 1.2em;
text-align: center;
}
.intro h1 {
line-height: 1.1em;
font-family: "DM Sans Bold";
font-size: 1.2em;
}
.intro code {
font-family: monospace;
font-size: 1.1em;
word-break: break-all;
}
.intro a, .intro a:active, .intro a:visited {
color: #00b4e4;
text-decoration: none;
word-break: break-all;
}
.advice {
width: 80%;
margin: auto;
margin-bottom: 15%;
margin-top: 5em;
background-color: #ffffff;
border-radius: 1rem;
border: 1px solid #b7b7b7;
padding: 2em;
font-family: "DM Sans";
font-size: .9em;
box-sizing: border-box;
align-items: normal;
}
.advice p {
margin-bottom: 1em;
}
.advice p:first-child {
margin-top: 0;
}
.advice p.list-intro {
margin: 0;
}
.advice ul {
list-style-type: square;
margin: 0;
padding: 0 1em;
}
.advice ul li {
line-height: 2em;
}
.advice p:last-child {
margin-bottom: 0;
}
/* sm: 640px+ */
@media (width >= 40rem) {
header {
margin-bottom: 1em;
margin-top: 5em;
}
header img {
width: 50%;
}
.intro h1 {
font-size: 2em;
}
.advice {
width: 50%;
}
}
/* xl: 1280px+ */
@media (width >= 80rem) {
.intro h1 {
font-size: 3.4em;
}
}
/* 2xl: 1536px+ */
@media (width >= 96rem) {
header img {
width: 25%;
min-width: 200px;
max-width: 500px;
max-height: 300px;
}
.advice {
width: 25%;
min-width: 200px;
min-width: 300px;
max-width: 500px;
}
}

View File

Binary file not shown.

View File

@@ -23,8 +23,7 @@ const Translations = {
return;
const errorMsg = `Error loading translations for language '${lang}': `;
const translationJsonUrl = import.meta.resolve(`./i18n/${lang}.json`);
this.promises[lang] = fetch(translationJsonUrl).then(async (resp) => {
this.promises[lang] = fetch(`./skin/i18n/${lang}.json`).then(async (resp) => {
if ( resp.ok ) {
this.data[lang] = JSON.parse(await resp.text());
} else {
@@ -191,40 +190,8 @@ function initUILanguageSelector(activeLanguage, languageChangeCallback) {
languageSelector.onchange = languageChangeCallback;
}
function parseDom(html) {
const domParser = new DOMParser();
return domParser.parseFromString(html, "text/html").documentElement;
}
function translatePageInWindow(w) {
if ( w.KIWIX_RESPONSE_TEMPLATE && w.KIWIX_RESPONSE_DATA ) {
const template = parseDom(w.KIWIX_RESPONSE_TEMPLATE).textContent;
// w.KIWIX_RESPONSE_DATA may belong to a different context and running
// I18n.render() on it directly won't work correctly
// because the type checks (obj.__proto__ == ???.prototype) in
// I18n.instantiateParameterizedMessages() will fail (String.prototype
// refers to different objects in different contexts).
// Work arround that issue by copying the object into our context.
const params = JSON.parse(JSON.stringify(w.KIWIX_RESPONSE_DATA));
const newHtml = I18n.render(template, params);
w.document.documentElement.innerHTML = parseDom(newHtml).innerHTML;
}
}
function translateSelf() {
if ( window.KIWIX_RESPONSE_TEMPLATE && window.KIWIX_RESPONSE_DATA ) {
setUserLanguage(getUserLanguage(), () => {
translatePageInWindow(window)
});
}
};
window.$t = $t;
window.getUserLanguage = getUserLanguage;
window.setUserLanguage = setUserLanguage;
window.initUILanguageSelector = initUILanguageSelector;
window.translatePageInWindow = translatePageInWindow;
window.I18n = I18n;
window.addEventListener('load', translateSelf);

View File

@@ -2,9 +2,6 @@
"@metadata": {
"authors": [
"Asma",
"Hamoudak",
"Meno25",
"Mohammed Qays",
"Ravan",
"محمد أحمد عبد الفتاح"
]
@@ -15,7 +12,7 @@
"no-book-found": "لا يوجد كتاب يطابق معايير الاختيار",
"url-not-found": "لم يتم العثور على عنوان URL المطلوب \"{{url}}\" على هذا الخادم.",
"suggest-search": "قم بإجراء بحث عن النص الكامل لـ <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "للأسف ! فشل في اختيار مقال عشوائي :(",
"random-article-failure": "مع الأسف! فشل اختيار مقال عشوائي :(",
"invalid-raw-data-type": "{{DATATYPE}} ليس طلبًا صالحًا للمحتوى الأولي.",
"no-value-for-arg": "لم يتم تقديم قيمة للوسيطة {{ARGUMENT}}",
"no-query": "لم يتم تقديم ملخص.",
@@ -24,32 +21,13 @@
"400-page-heading": "طلب غير صالح",
"404-page-title": "المحتوى غير موجود",
"404-page-heading": "لم يتم العثور عليه",
"500-page-title": "خطأ داخلي بالخادم",
"500-page-heading": "عفواً، الصفحة لا تعمل.",
"500-page-text": "لا يمكن إرسال المسار المطلوب بشكل صحيح:",
"fulltext-search-unavailable": "البحث في كامل النص غير متاح",
"no-search-results": "محرك البحث في كامل النص غير متوفر لهذا المحتوي.",
"library-button-text": "توجه إلي صفحة الترحيب",
"home-button-text": "انتقل إلى الصفحة الرئيسة لـ '{{{BOOK_TITLE}}}'",
"500-page-title": "خطأ في الخادم الداخلي",
"500-page-heading": "خطأ في الخادم الداخلي",
"fulltext-search-unavailable": "البحث عن النص الكامل غير متاح",
"no-search-results": "محرك البحث عن النص الكامل غير متاح لهذا المحتوى.",
"library-button-text": "اذهب لصفحة الترحيب",
"home-button-text": "انتقل إلى الصفحة الرئيسية لـ \"{{BOOK_TITLE}}\"",
"random-page-button-text": "اذهب إلى صفحة عشوائية",
"searchbox-tooltip": "ابحث عن '{{{BOOK_TITLE}}}'",
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة.",
"direct-download-alt-text": "التنزيل مباشرة عبر بروتوكول HTTP(S)",
"hash-download-link-text": "المجموع الإختباري لخوارزمية SHA-256",
"hash-download-alt-text": "عرض المجموع الإختباري لخوارزمية SHA-256 للملف",
"magnet-link-text": "رابط جاذب",
"magnet-alt-text": "التنزيل بواسطة الرابط الجاذب",
"torrent-download-link-text": "بت تورنت",
"torrent-download-alt-text": "التنزيل بواسطة بت تورنت",
"unknown-error": "خطأ غير معروف",
"book-category.wikibooks": "ويكي الكتب",
"book-category.wikinews": "ويكي الأخبار",
"book-category.wikipedia": "ويكيبيديا",
"book-category.wikiquote": "ويكي الإقتباس",
"book-category.wikisource": "ويكي مصدر",
"book-category.wikispecies": "ويكي أنواع",
"book-category.wikiversity": "ويكي الجامعة",
"book-category.wikivoyage": "ويكي الرحلات",
"book-category.wiktionary": "ويكي القاموس",
"book-category.other": "أخرى {كتب أخري}"
"searchbox-tooltip": "بحث \"{{BOOK_TITLE}}\"",
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة."
}

View File

@@ -1,24 +1,13 @@
{
"@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": "স্বাগত পাতায় চলুন",

View File

@@ -1,11 +1,9 @@
{
"@metadata": {
"authors": [
"AnnaTheProgrammer777",
"IMayBeABitShy",
"Justman10000",
"Lucas Werkmeister",
"MoritzMT20",
"Rofiatmustapha12",
"ThisCarthing"
]
@@ -38,9 +36,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": "'{{{BOOK_TITLE}}}' durchsuchen",
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen",
"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 &nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
@@ -58,22 +56,12 @@
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Herunterladen über BitTorrent",
"library-opds-feed-all-entries": "ODPS Feed der Bibliothek - Alle Einträge",
"filter-by-tag": "Nach Tag \"{{{TAG}}}\" filtern",
"stop-filtering-by-tag": "Filterung nach Tag \"{{{TAG}}}\" aufheben",
"filter-by-tag": "Nach Tag \"{{TAG}}\" filtern",
"stop-filtering-by-tag": "Filterung nach Tag \"{{TAG}}\" aufheben",
"library-opds-feed-parameterised": "ODPS Feed der Bibliothek - Einträge mit {{#LANG}\nSprache {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}}{{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Wilkommen beim Kiwix Server",
"download-links-heading": "Download Links für <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Buch herunterladen",
"preview-book": "Vorschau",
"unknown-error": "Unbekannter Fehler",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionary",
"book-category.other": "Andere"
"unknown-error": "Unbekannter Fehler"
}

View File

@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
"Alhaji Yakubu"
"Alhaji Yakubu",
"Amire80"
]
},
"welcome-page-overzealous-filter": "Duoro kyebe. <a href=\"{{URL}}\">E na boɔra ka fo</a>?",

View File

@@ -1,18 +1,13 @@
{
"@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&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Αναζήτηση",
@@ -24,10 +19,10 @@
"direct-download-alt-text": "άμεση λήψη",
"hash-download-alt-text": "λήψη αναγνωριστικού",
"magnet-alt-text": "λήψη μαγνήτη",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Λήψη μέσω BitTorrent",
"filter-by-tag": ιλτράρισμα κατά ετικέτα \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Διακοπή φιλτραρίσματος κατά ετικέτα \"{{{TAG}}}\"",
"torrent-download-link-text": "Αρχείο torrent",
"torrent-download-alt-text": "λήψη torrent",
"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": "Κατεβάστε το βιβλίο",

View File

@@ -20,25 +20,9 @@
, "400-page-heading" : "Invalid request"
, "404-page-title" : "Content not found"
, "404-page-heading" : "Not Found"
, "new-404-page-title" : "Page not found"
, "new-404-page-heading" : "Oops. Page not found."
, "404-img-text": "Not found!"
, "path-was-not-found": "The requested path was not found:"
, "404-advice.p1": "The content you're looking for may still be available, but it might be located at a different place within the ZIM file."
, "404-advice.p2": "Please:"
, "404-advice.p3": "Try using the search function to find the content you want"
, "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" : "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"
, "external-link-advice.p1": "The link you're trying to access is not part of your offline package and requires an internet connection."
, "external-link-advice.p2": "If you can go online, you can attempt to open the link."
, "external-link-advice.p3": "You can otherwise return to your ZIM's offline content by using your browser's back button."
, "500-page-heading" : "Internal Server Error"
, "500-page-text": "An internal server error occured. We are sorry about that :/"
, "fulltext-search-unavailable" : "Fulltext search unavailable"
, "no-search-results": "The fulltext search engine is not available for this content."
, "search-results-page-title": "Search: {{SEARCH_PATTERN}}"
@@ -47,9 +31,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&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>"
@@ -94,5 +78,4 @@
, "book-category.wikivoyage": "Wikivoyage"
, "book-category.wiktionary": "Wiktionary"
, "book-category.other": "Other"
, "text-loading-content": "Loading Content"
}

View File

@@ -3,23 +3,20 @@
"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 :(",
"invalid-raw-data-type": "{{DATATYPE}} no es una solicitud válida de contenido en crudo.",
"invalid-request": "La URL solicitada \"{{{url}}}\" no es una solicitud válida.",
"no-value-for-arg": "No se ha proporcionado ningún valor para el argumento {{ARGUMENT}}",
"no-query": "No se ha proporcionado ninguna consulta.",
"raw-entry-not-found": "No se puede encontrar la entrada {{DATATYPE}} {{ENTRY}}",
@@ -27,36 +24,14 @@
"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": "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.",
"500-page-heading": "Error interno del servidor",
"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}}",
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "No se encontraron resultados para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"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&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
@@ -66,39 +41,19 @@
"count-of-matching-books": "{{COUNT}} libro(s)",
"download": "Descargar",
"direct-download-link-text": "Directamente",
"direct-download-alt-text": "Descargar directamente vía HTTP(S)",
"hash-download-link-text": "Suma de verificación SHA-256",
"hash-download-alt-text": "Mostrar la suma de verificación de archivos SHA-256",
"direct-download-alt-text": "descarga directa",
"hash-download-link-text": "hash sha256",
"hash-download-alt-text": "descargar hash",
"magnet-link-text": "Enlace magnético",
"magnet-alt-text": "Descargar mediante enlace Magnet",
"magnet-alt-text": "Descargar link magnético",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Descargar a través de BitTorrent",
"library-opds-feed-all-entries": "Biblioteca OPDS Feed - Todas las entradas",
"filter-by-tag": "Filtrar por etiqueta \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Dejar de filtrar por etiqueta \"{{{TAG}}}\"",
"filter-by-tag": "Filtrar por etiqueta \"{{TAG}}\"",
"stop-filtering-by-tag": "Dejar de filtrar por etiqueta \"{{TAG}}\"",
"library-opds-feed-parameterised": "Feed OPDS de la biblioteca: entradas que coinciden con {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nEtiqueta: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bienvenido al servidor Kiwix",
"download-links-heading": "Enlaces de descarga para <b><i>{{BOOK_TITLE}}</i></b>",
"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",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikiespecies",
"book-category.wikiversity": "Wikiversidad",
"book-category.wikivoyage": "Wikiviajes",
"book-category.wiktionary": "Wikcionario",
"book-category.other": "Otros",
"text-loading-content": "Cargando contenido"
"unknown-error": "Error desconocido"
}

View File

@@ -3,8 +3,7 @@
"authors": [
"MITO",
"Nike",
"Pyscowicz",
"Samoasambia"
"Pyscowicz"
]
},
"name": "suomi",
@@ -15,22 +14,13 @@
"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": "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}}",
"500-page-heading": "Sisäinen palvelinvirhe",
"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 kirjasta '{{{BOOK_TITLE}}}'",
"searchbox-tooltip": "Hae '{{BOOK_TITLE}}'",
"search": "Hae",
"book-filtering-all-categories": "Kaikki luokat",
"book-filtering-all-languages": "Kaikki kielet",
@@ -38,21 +28,10 @@
"download": "Lataa",
"magnet-link-text": "Magnet-linkki",
"magnet-alt-text": "lataa magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-link-text": "Torrent-tiedosto",
"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",
"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öä"
"unknown-error": "Tuntematon virhe"
}

View File

@@ -3,7 +3,6 @@
"authors": [
"Adriendelucca",
"Benoit74",
"Derugon",
"Gomoko",
"Goombiis",
"Melimeli",
@@ -32,25 +31,9 @@
"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é na 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 dutiliser 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 dorigine ne fonctionne pas correctement.",
"500-page-title": "Erreur interne du serveur",
"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 douvrir 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.",
"500-page-heading": "Erreur interne du serveur",
"500-page-text": "Une erreur de serveur interne s'est produite. Nous en sommes désolés :/",
"fulltext-search-unavailable": "Recherche en texte intégral non disponible",
"no-search-results": "Le moteur de recherche en texte intégral nest pas disponible pour ce contenu.",
"search-results-page-title": "Rechercher : {{SEARCH_PATTERN}}",
@@ -59,9 +42,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>",
@@ -96,6 +79,5 @@
"book-category.wikiversity": "Wikiversité",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionnaire",
"book-category.other": "Autre",
"text-loading-content": "Chargement du contenu"
"book-category.other": "Autre"
}

View File

@@ -22,19 +22,9 @@
"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-img-text": "הדף לא עובד",
"external-link-detected": "התגלה קישור חיצוני",
"caution-warning": "זהירות!",
"external-link-advice.p2": "אם יש לך איך להתחבר לאינטרנט, אפשר לנסות לפתוח את הקישור.",
"500-page-heading": "שגיאת שרת פנימית",
"500-page-text": "אירעה שגיאת שרת פנימית. אנחנו מצטערים על זה :/",
"fulltext-search-unavailable": "חיפוש בטקסט מלא אינו זמין",
"no-search-results": "מנוע החיפוש בטקסט מלא אינו זמין עבור התוכן הזה.",
"search-results-page-title": "חיפוש: {{SEARCH_PATTERN}}",
@@ -43,9 +33,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": "מופעל על־ידי&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
@@ -82,6 +72,5 @@
"book-category.wikiversity": "ויקיברסיטה",
"book-category.wikivoyage": "ויקימסע",
"book-category.wiktionary": "ויקימילון",
"book-category.other": "אחר",
"text-loading-content": "התוכן נטען"
"book-category.other": "אחר"
}

View File

@@ -1,39 +0,0 @@
{
"@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"
}

View File

@@ -13,7 +13,6 @@
"400-page-heading": "Անվավեր հարցում",
"404-page-title": "Սխալ հասցե",
"404-page-heading": "Սխալ հասցե",
"500-img-text": "Էջը չի աշխատում",
"library-button-text": "Գրադարանի էջ",
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
"random-page-button-text": "Բացել պատահական էջ",

View File

@@ -13,7 +13,7 @@
"url-not-found": "Le URL reuqestate \"{{url}}\" non ha essite trovate sur iste servitor.",
"suggest-search": "Facer un recerca in texto complete de <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Non poteva eliger un articulo aleatori :(",
"invalid-raw-data-type": "{{DATATYPE}} non es un requesta valide pro contento brute.",
"invalid-raw-data-type": "{{DATATYPE}} non es un request valide pro contento crude.",
"invalid-request": "Le URL requestate “{{{url}}}” non es un requesta valide.",
"no-value-for-arg": "Necun valor fornite pro le argumento {{ARGUMENT}}",
"no-query": "Necun consulta fornite.",
@@ -22,25 +22,9 @@
"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": "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.",
"500-page-heading": "Error interne del servitor",
"500-page-text": "Un error interne del servitor ha occurrite. Nos lo regretta :/",
"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}}",
@@ -49,9 +33,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 in {{{BOOK_TITLE}}}",
"searchbox-tooltip": "Cercar '{{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&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
@@ -76,16 +60,5 @@
"download-links-heading": "Discargar ligamines pro <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Discargar libro",
"preview-book": "Previsualisation",
"unknown-error": "Error incognite",
"book-category.wikibooks": "Wikilibros",
"book-category.wikinews": "Wikinovas",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversitate",
"book-category.wikivoyage": "Wikiviage",
"book-category.wiktionary": "Wiktionario",
"book-category.other": "Altere",
"text-loading-content": "Carga contento"
"unknown-error": "Error incognite"
}

View File

@@ -1,74 +0,0 @@
{
"@metadata": {
"authors": [
"Akmaie Ajam"
]
},
"name": "Bahasa Inggris",
"suggest-full-text-search": "mengandung '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Tidak ada buku seperti ini: {{BOOK_NAME}}",
"too-many-books": "Terlalu banyak buku yang diminta ({{NB_BOOKS}}) dimana batasnya adalah {{LIMIT}}",
"no-book-found": "Tidak ada buku yang sesuai kriteria yang dipilih",
"url-not-found": "URL yang diminta \"{{url}}\" tidak ditemukan di server ini.",
"suggest-search": "Lakukan pencarian teks lengkap untuk <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Waduh! Gagal memilih artikel acak :(",
"invalid-raw-data-type": "{{DATATYPE}} bukan permintaan yang sah untuk konten mentah.",
"invalid-request": "URL yang diminta \"{{{url}}}\" bukan permintaan yang sah.",
"no-value-for-arg": "Tidak ada nilai yang diberikan untuk argumen {{ARGUMENT}}",
"no-query": "Tidak ada kueri yang diberikan.",
"raw-entry-not-found": "Tidak dapat menemukan entri {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Permintaan tidak sah",
"400-page-heading": "Permintaan tidak sah",
"404-page-title": "Konten tidak ditemukan",
"404-page-heading": "Tidak Ditemukan",
"500-page-title": "Kesalahan Server Internal",
"500-page-heading": "Kesalahan Server Internal",
"500-page-text": "Terjadi kesalahan server internal. Kami mohon maaf atas hal ini :/",
"fulltext-search-unavailable": "Pencarian teks lengkap tidak tersedia",
"no-search-results": "Mesin pencari teks lengkap tidak tersedia untuk konten ini.",
"search-results-page-title": "Pencarian: {{SEARCH_PATTERN}}",
"search-results-page-header": "Hasil <b>{{START}}-{{END}}</b> dari <b>{{COUNT}}</b> untuk <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Tidak ada hasil yang ditemukan untuk <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "dari {{BOOK_TITLE}}",
"word-count": "{{COUNT}} kata",
"library-button-text": "Pergi ke halaman selamat datang",
"home-button-text": "Buka halaman utama '{{BOOK_TITLE}}'",
"random-page-button-text": "Buka halaman yang dipilih secara acak",
"searchbox-tooltip": "Cari '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Dua buku atau lebih dalam bahasa berbeda dapat muncul dalam hasil pencarian, yang dapat memicu hasil yang membingungkan.",
"welcome-page-overzealous-filter": "Tidak ada hasil. Apakah Anda ingin <a href=\"{{URL}}\">mengatur ulang filter</a>?",
"powered-by-kiwix-html": "Didukung oleh <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Cari",
"book-filtering-all-categories": "Semua kategori",
"book-filtering-all-languages": "Semua bahasa",
"count-of-matching-books": "{{COUNT}} buku",
"download": "Unduh",
"direct-download-link-text": "Langsung",
"direct-download-alt-text": "Unduh langsung melalui HTTP(S)",
"hash-download-link-text": "Checksum SHA-256",
"hash-download-alt-text": "Tampilkan checksum berkas SHA-256",
"magnet-link-text": "Tautan magnet",
"magnet-alt-text": "Unduh melalui tautan Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Unduh melalui BitTorrent",
"library-opds-feed-all-entries": "Umpan OPDS Perpustakaan - Semua entri",
"filter-by-tag": "Saring berdasarkan tag \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Berhenti penyaringan berdasarkan tag \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "Umpan OPDS Perpustakaan - entri yang cocok dengan {{#LANG}}\nBahasa: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nKueri: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Selamat datang di Server Kiwix",
"download-links-heading": "Tautan unduhan untuk <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Unduh buku",
"preview-book": "Pratayang",
"unknown-error": "Kesalahan yang tidak diketahui",
"book-category.wikibooks": "Wikibuku",
"book-category.wikinews": "Wikiberita",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikikutip",
"book-category.wikisource": "Wikisumber",
"book-category.wikispecies": "Wikispesies",
"book-category.wikiversity": "Wikiversitas",
"book-category.wikivoyage": "Wikiwisata",
"book-category.wiktionary": "Wikikamus",
"book-category.other": "Lainnya",
"text-loading-content": "Memuat Konten"
}

View File

@@ -3,10 +3,8 @@
"authors": [
"Albano",
"Beta16",
"Clorofolle",
"Luca.favorido",
"McDutchie",
"Wheelygay"
"McDutchie"
]
},
"name": "italiano",
@@ -23,47 +21,27 @@
"400-page-heading": "Richiesta non valida",
"404-page-title": "Contenuto non trovato",
"404-page-heading": "Non trovato",
"new-404-page-title": "Pagina non trovata",
"new-404-page-heading": "Oops. Pagina non trovata.",
"404-img-text": "Non trovato!",
"500-page-title": "Errore interno del server",
"500-page-heading": "Oops. La pagina non funziona.",
"500-page-text": "Il percorso richiesto non può essere fornito correttamente:",
"caution-warning": "Attenzione!",
"500-page-heading": "Errore interno del server",
"500-page-text": "Si è verificato un errore interno del server. Ci dispiace :/",
"search-results-page-title": "Cerca: {{SEARCH_PATTERN}}",
"search-results-page-header": "Risultati <b>{{START}}-{{END}}</b> di <b>{{COUNT}}</b> per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Non è stato trovato alcun risultato per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "da {{BOOK_TITLE}}",
"word-count": "{{COUNT}} parole",
"library-button-text": "Vai alla pagina di benvenuto",
"home-button-text": "Vai alla pagina principale di '{{{BOOK_TITLE}}}'",
"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}}}'",
"welcome-page-overzealous-filter": "Nessun risultato. Vuoi <a href=\"{{URL}}\">reimpostare il filtro</a>?",
"search": "Cerca",
"searchbox-tooltip": "Cerca '{{BOOK_TITLE}}'",
"book-filtering-all-categories": "Tutte le categorie",
"book-filtering-all-languages": "Tutte le lingue",
"count-of-matching-books": "{{COUNT}} libro/i",
"download": "Scarica",
"direct-download-link-text": "Download diretto",
"direct-download-alt-text": "Scarica direttamente tramite HTTP(S)",
"magnet-alt-text": "Scarica tramite collegamento Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Scarica tramite BitTorrent",
"welcome-to-kiwix-server": "Benvenuti al server Kiwix",
"download-links-heading": "Link per scaricare <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Scarica libro",
"preview-book": "Anteprima",
"unknown-error": "Errore sconosciuto",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinotizie",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversità",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wikizionario",
"book-category.other": "Altro",
"text-loading-content": "Caricamento contenuto"
"unknown-error": "Errore sconosciuto"
}

View File

@@ -1,80 +1,21 @@
{
"@metadata": {
"authors": [
"YeBoy371",
"Ykhwong"
]
},
"name": "한국어",
"suggest-full-text-search": "'{{{SEARCH_TERMS}}}' 포함...",
"no-such-book": "해당 책이 없습니다: {{BOOK_NAME}}",
"too-many-books": "요청된 책이 너무 많습니다. ({{NB_BOOKS}}) 한도는 {{LIMIT}}입니다.",
"no-book-found": "선택 기준에 해당하는 책이 없습니다",
"url-not-found": "\"{{url}}\" 요청 URL은 이 서버에서 찾을 수 없습니다.",
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>에 대한 전문 검색을 수행해 보세요",
"random-article-failure": "이런! 임의 문서를 선택하지 못했습니다 :(",
"invalid-raw-data-type": "{{DATATYPE}} 값은 원시 콘텐츠에 대한 유효한 요청이 아닙니다.",
"invalid-request": "\"{{{url}}}\" 요청 URL은 유효한 요청이 아닙니다.",
"no-value-for-arg": "{{ARGUMENT}} 인수에 지정된 값이 없습니다",
"no-query": "지정된 쿼리가 없습니다.",
"raw-entry-not-found": "{{DATATYPE}} 항목인 {{ENTRY}} 항목을 찾을 수 없습니다",
"400-page-title": "잘못된 요청",
"400-page-heading": "잘못된 요청",
"404-page-title": "내용이 없습니다",
"404-page-heading": "찾을 수 없음",
"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-img-text": "문서가 동작하지 않습니다",
"external-link-detected": "외부 링크가 발견되었습니다",
"caution-warning": "경고!",
"500-page-heading": "내부 서버 오류",
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
"no-search-results": "이 콘텐츠에는 전문 검색 엔진을 사용할 수 없습니다.",
"search-results-page-title": "검색: {{SEARCH_PATTERN}}",
"search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b>에 대한 <b>{{COUNT}}</b>개 중 <b>{{START}}-{{END}}</b> 결과",
"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}}}' 검색",
"welcome-page-overzealous-filter": "결과가 없습니다. <a href=\"{{URL}}\">필터를 재설정</a>하시겠습니까?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a>에서 제공",
"search": "검색",
"book-filtering-all-categories": "모든 분류",
"book-filtering-all-languages": "모든 언어",
"count-of-matching-books": "책 {{COUNT}}권",
"download": "다운로드",
"direct-download-link-text": "직접",
"direct-download-alt-text": "HTTP(S)를 통해 직접 다운로드",
"hash-download-link-text": "SHA-256 체크섬",
"hash-download-alt-text": "SHA-256 파일 체크섬 표시",
"magnet-link-text": "마그넷 링크",
"magnet-alt-text": "마그넷 링크를 통해 다운로드",
"torrent-download-link-text": "비트토렌트",
"torrent-download-alt-text": "비트토렌트를 통해 다운로드",
"library-opds-feed-all-entries": "라이브러리 OPDS 피드 - 모든 항목",
"filter-by-tag": "\"{{{TAG}}}\" 태그로 필터링",
"stop-filtering-by-tag": "\"{{{TAG}}}\" 태그로 필터링 중지",
"library-opds-feed-parameterised": "라이브러리 OPDS 피드 - {{#LANG}} 일치 항목\n언어: {{LANG}} {{/LANG}}{{#CATEGORY}}\n분류: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n태그: {{TAG}} {{/TAG}}{{#Q}}\n쿼리: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Kiwix Server에 오신 것을 환영합니다",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b>의 링크 다운로드",
"download-links-title": "책 다운로드",
"preview-book": "미리 보기",
"unknown-error": "알 수 없는 오류",
"book-category.wikibooks": "위키책",
"book-category.wikinews": "위키뉴스",
"book-category.wikipedia": "위키백과",
"book-category.wikiquote": "위키인용집",
"book-category.wikisource": "위키문헌",
"book-category.wikispecies": "위키생물종",
"book-category.wikiversity": "위키배움터",
"book-category.wikivoyage": "위키여행",
"book-category.wiktionary": "위키낱말사전",
"book-category.other": "기타"
"preview-book": "미리 보기"
}

View File

@@ -15,24 +15,18 @@
"random-article-failure": "Ups! Et konnt keen zoufällegen Artikel ausgewielt ginn :(",
"404-page-title": "Inhalt net fonnt",
"404-page-heading": "Net fonnt",
"new-404-page-title": "Säit net fonnt",
"new-404-page-heading": "Ups. Säit net fonnt.",
"404-img-text": "Net fonnt!",
"500-page-title": "Interne Feeler um Server",
"500-page-heading": "Ups. D'Säit funktionéiert net.",
"500-page-heading": "Interne Feeler um Server",
"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}}",
"search-results-page-header": "Resultater <b>{{START}}-{{END}}</b> vu(n) <b>{{COUNT}}</b> fir <b>„{{{SEARCH_PATTERN}}}“</b>",
"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": "Op d'Haaptsäit vun '{{{BOOK_TITLE}}}' goen",
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
"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",

View File

@@ -22,25 +22,9 @@
"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-img-text": "Страницата не работи.",
"external-link-detected": "Најдена е надворешна врска",
"caution-warning": "Внимание!",
"external-link-intro": "На пат сте да го напуштите ZIM-читачот на Кивикс за да појдете на",
"external-link-advice.p1": "Врската што пробувате да ја отворите не е дел од вашиот вонмрежен пакет и бара семрежна врска.",
"external-link-advice.p2": "Ако можете да се поврзете со семрежнето, пробајте да ја отворите врската.",
"external-link-advice.p3": "Во спротивно можете повторно да пробате да ја отворите вонмрежната содржина на вашиот ZIM стискајќи на копчето за враќање назад на вашиот прелистувач.",
"500-page-heading": "Внатрешна грешка во опслужувачот",
"500-page-text": "Настана внатрешна грешка во опслужувачот. Жал ни е :/",
"fulltext-search-unavailable": "Целотекстното пребарување е недостапно",
"no-search-results": "Погонот за целотекстно пребарување не е достапен за оваа содржина.",
"search-results-page-title": "Пребарување: {{SEARCH_PATTERN}}",
@@ -49,9 +33,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": "Овозможено од&nbsp;<a href=\"https://kiwix.org\">Кивикс</a>",
@@ -94,6 +78,5 @@
"book-category.wikiversity": "Викиуниверзитет",
"book-category.wikivoyage": "Википатување",
"book-category.wiktionary": "Викиречник",
"book-category.other": "друго",
"text-loading-content": "Ја вчитувам содржината"
"book-category.other": "друго"
}

View File

@@ -1,74 +0,0 @@
{
"@metadata": {
"authors": [
"TorgeirS"
]
},
"name": "Engelsk",
"suggest-full-text-search": "inneholder '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Finner ingen slik bok: {{BOOK_NAME}}",
"too-many-books": "Det er forespurt for mange bøker {{NB_BOOKS}} der grensen er {{LIMIT}}",
"no-book-found": "Ingen bøker med treff på utvalgkriteriene",
"url-not-found": "Den forespurte webadressen «{{url}}» ble ikke funnet på denne serveren.",
"suggest-search": "Gjør et fulltekstsøk etter <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oops! Klarte ikke å hente en tilfeldig artikkel :(",
"invalid-raw-data-type": "{{DATATYPE}} er ikke en gyldig forespørsel etter uformatert innhold.",
"invalid-request": "Den forespurte URL-en \"{{{url}}}\" er ikke en gyldig forespørsel.",
"no-value-for-arg": "Ingen verdi angitt for argumentet {{ARGUMENT}}",
"no-query": "Ingen søkeord oppgitt.",
"raw-entry-not-found": "Finner ikke {{DATATYPE}}-oppføringen {{ENTRY}}",
"400-page-title": "Ugyldig forespørsel",
"400-page-heading": "Ugyldig forespørsel",
"404-page-title": "Finner ikke innhold",
"404-page-heading": "Ikke funnet",
"500-page-title": "Intern serverfeil",
"500-page-heading": "Intern serverfeil",
"500-page-text": "Det oppstod en intern serverfeil. Vi beklager det :/",
"fulltext-search-unavailable": "Fulltekstsøk utilgjengelig",
"no-search-results": "Søkemotoren for fulltekstsøk er ikke tilgjengelig for dette innholdet.",
"search-results-page-title": "Søk: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultater <b>{{START}}-{{END}}</b> av <b>{{COUNT}}</b> for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Ingen resultater ble funnet for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "fra {{BOOK_TITLE}}",
"word-count": "{{COUNT}} ord",
"library-button-text": "Gå til velkomstsiden",
"home-button-text": "Gå til hovedsiden for '{{BOOK_TITLE}}'",
"random-page-button-text": "Gå til en tilfeldig valgt side",
"searchbox-tooltip": "Søk etter '{{BOOK_TITLE}}'",
"confusion-of-tongues": "To eller flere bøker på forskjellige språk vil inngå i søket, noe som kan gi forvirrende resultater.",
"welcome-page-overzealous-filter": "Ingen resultater. Vil du <a href=\"{{URL}}\">tilbakestille filteret</a>?",
"powered-by-kiwix-html": "Drevet av <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Søk",
"book-filtering-all-categories": "Alle kategorier",
"book-filtering-all-languages": "Alle språk",
"count-of-matching-books": "{{COUNT}} bok/bøker",
"download": "Last ned",
"direct-download-link-text": "Direkte",
"direct-download-alt-text": "Last ned direkte via HTTP(S)",
"hash-download-link-text": "SHA-256 kontrollsum",
"hash-download-alt-text": "Vis filens SHA-256 kontrollsum",
"magnet-link-text": "Magnetlenke",
"magnet-alt-text": "Last ned via Magnetlenke",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Last ned via BitTorrent",
"library-opds-feed-all-entries": "Biblioteks OPDS-feed - Alle oppføringer",
"filter-by-tag": "Filtrer etter taggen \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Slutt å filtrere etter taggen \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "Biblioteks OPDS-feed - oppføringer som samsvarer med {{#LANG}}\nSpråk: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTagg: {{TAG}} {{/TAG}}{{#Q}}\nSpørring: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Velkommen til Kiwix Server",
"download-links-heading": "Nedlastingslenker for <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Last ned bok",
"preview-book": "Forhåndsvisning",
"unknown-error": "Ukjent feil",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionary",
"book-category.other": "Annet",
"text-loading-content": "Laster innhold"
}

View File

@@ -1,7 +1,6 @@
{
"@metadata": {
"authors": [
"ABPMAB",
"Kelson",
"McDutchie",
"Siebrand",
@@ -17,7 +16,6 @@
"suggest-search": "In volledige tekst zoeen naar <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oeps! Kan geen willekeurig artikel kiezen :(",
"invalid-raw-data-type": "{{DATATYPE}} is geen geldig verzoek voor onbewerkte inhoud.",
"invalid-request": "De gevraagde URL “{{{url}}}” is onjuist.",
"no-value-for-arg": "Er is geen waarde opgegeven bij {{ARGUMENT}}",
"no-query": "Er is geen zoekterm opgegeven.",
"raw-entry-not-found": "Kan het {{DATATYPE}}-item {{ENTRY}} niet vinden",
@@ -25,36 +23,18 @@
"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": "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.",
"500-page-heading": "Interne serverfout",
"500-page-text": "Er is een interne serverfout opgetreden. Dat spijt ons",
"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}}",
"search-results-page-header": "Resultaten <b>{{START}}-{{END}}</b> van <b>{{COUNT}}</b> voor <b>“{{{SEARCH_PATTERN}}}”</b>",
"empty-search-results-page-header": "Er zijn geen resultaten gevonden voor <b>“{{{SEARCH_PATTERN}}}”</b>",
"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": "{{{BOOK_TITLE}}} doorzoeken",
"searchbox-tooltip": "Naar {{BOOK_TITLE}} zoeken",
"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>",
@@ -72,23 +52,12 @@
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Downloaden via BitTorrent",
"library-opds-feed-all-entries": "OPDS-feed bibliotheek: alle vermeldingen",
"filter-by-tag": "Filteren op label “{{{TAG}}}”",
"stop-filtering-by-tag": "Niet meer filteren op label “{{{TAG}}}”",
"filter-by-tag": "Filteren op label “{{TAG}}”",
"stop-filtering-by-tag": "Niet meer filteren op label “{{TAG}}”",
"library-opds-feed-parameterised": "OPDS-feed bibliotheek: vermeldingen die overeenkomen met {{#LANG}}\nTaal: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nLabel: {{TAG}} {{/TAG}}{{#Q}}\nZoekopdracht: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Boek downloaden",
"preview-book": "Voorvertoning",
"unknown-error": "Onbekende fout",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinieuws",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "WikiWoordenboek",
"book-category.other": "Overige",
"text-loading-content": "Inhoud laden"
"unknown-error": "Onbekende fout"
}

View File

@@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Amire80",
"Lancine.kounfantoh.fofana"
]
},

View File

@@ -2,7 +2,6 @@
"@metadata": {
"authors": [
"Eduardoaddad",
"Klgor1803",
"Obiru",
"Re demz",
"YoReaper"
@@ -10,16 +9,6 @@
},
"name": "Português",
"suggest-full-text-search": "Contendo '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Não existe o livro: {{BOOK_NAME}}",
"too-many-books": "Muitos livros solicitados {{NB_BOOKS}} mas o limite é {{LIMIT}}",
"no-book-found": "Nenhum livro corresponde os critérios de seleção",
"url-not-found": "O URL \"{{url}}\" não foi encontrado neste servidor.",
"suggest-search": "Fazer uma pesquisa de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ops! Falha ao escolher um artigo aleatório :(",
"invalid-raw-data-type": "{{DATATYPE}} não é uma solicitação válida para conteúdo bruto.",
"invalid-request": "O URL solicitado \"{{{url}}}\" não é uma solicitação válida.",
"no-value-for-arg": "Nenhum valor para o argumento {{ARGUMENT}}",
"no-query": "Nenhuma consulta fornecida.",
"400-page-title": "Requisição inválida",
"400-page-heading": "Requisição inválida",
"404-page-title": "Conteúdo não encontrado",
@@ -27,9 +16,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}}",
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Nenhum resultado encontrado para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
@@ -40,37 +27,18 @@
"random-page-button-text": "Ir para uma página aleatória",
"searchbox-tooltip": "Buscar '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Dois ou mais livros em diferentes idiomas podem participar da pesquisa, isso pode proporcionar resultados confusos.",
"welcome-page-overzealous-filter": "Nenhum resultado. Gostaria de <a href=\"{{URL}}\">redefinir o filtro</a> ?",
"powered-by-kiwix-html": "Desenvolvido por <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Pesquisar",
"book-filtering-all-categories": "Todas as categorias",
"book-filtering-all-languages": "Todos os idiomas",
"count-of-matching-books": "{{COUNT}} livro(s)",
"download": "Baixar",
"direct-download-link-text": "Direto",
"direct-download-alt-text": "Baixar diretamente por HTTP(S)",
"hash-download-link-text": "Verificação SHA-256",
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
"magnet-link-text": "Link Magnet",
"magnet-alt-text": "Baixar por link Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Baixar via BitTorrent",
"library-opds-feed-all-entries": "Feed OPDS da biblioteca - Todas as entradas",
"filter-by-tag": "Filtrar por etiqueta \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Parar de filtrar por etiqueta \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "Biblioteca OPDS Feed - entradas que correspondem a {{#LANG}}\nIdioma: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bem vindo ao servidor Kiwix",
"download-links-heading": "Links para baixar <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Download do livro",
"preview-book": "Pré-visualizar",
"unknown-error": "Erro desconhecido",
"book-category.wikibooks": "Wikilivros",
"book-category.wikinews": "Wikinotícias",
"book-category.wikipedia": "Wikipédia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispécies",
"book-category.wikiversity": "Wikiversidade",
"book-category.wiktionary": "Wikcionário",
"book-category.other": "Outro"
"unknown-error": "Erro desconhecido"
}

View File

@@ -24,25 +24,9 @@
"400-page-heading": "Heading of the 400 error page",
"404-page-title": "Title of the 404 error page",
"404-page-heading": "Heading of the 404 error page",
"new-404-page-title": "Title of the 404 error page",
"new-404-page-heading": "Heading of the 404 error page",
"404-img-text": "Fallback text for the image on the 404 error page",
"path-was-not-found": "Message telling that the URL path was not found (to be followed by the actual path)",
"404-advice.p1": "1st paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p2": "2nd paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p3": "3rd paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p4": "4th paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p5": "5th paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"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)",
"external-link-advice.p1": "1st paragraph of the multiline advice on the external link blocker page (see external-link-advice.p1 through external-link-advice.p3 for full text)",
"external-link-advice.p2": "2nd paragraph of the multiline advice on the external link blocker page (see external-link-advice.p1 through external-link-advice.p3 for full text)",
"external-link-advice.p3": "3rd paragraph of the multiline advice on the external link blocker page (see external-link-advice.p1 through external-link-advice.p3 for full text)",
"fulltext-search-unavailable": "Title of the error page returned when search is attempted in a book without fulltext search database",
"no-search-results": "Text of the error page returned when search is attempted in a book without fulltext search database",
"search-results-page-title": "Title of the search results page",
@@ -96,6 +80,5 @@
"book-category.wikiversity": "Name for the category of Wikiversity books",
"book-category.wikivoyage": "Name for the category of Wikivoyage books",
"book-category.wiktionary": "Name for the category of Wiktionary books",
"book-category.other": "Books not belonging to any special category are listed under this one",
"text-loading-content": "Text displayed while content is being loaded"
"book-category.other": "Books not belonging to any special category are listed under this one"
}

View File

@@ -1,75 +0,0 @@
{
"@metadata": {
"authors": [
"MSClaudiu",
"Trotinel Iftode"
]
},
"name": "Engleză",
"suggest-full-text-search": "care conține '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Nu există o astfel de carte: {{BOOK_NAME}}",
"too-many-books": "Prea multe cărți solicitate ({{NB_BOOKS}}) unde limita este {{LIMIT}}",
"no-book-found": "Nicio carte nu corespunde criteriilor de selecție",
"url-not-found": "Adresa URL solicitată \"{{url}}\" nu a fost găsită pe acest server.",
"suggest-search": "Efectuați o căutare text complet pentru <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Hopa! Nu s-a putut alege un articol aleatoriu :(",
"invalid-raw-data-type": "{{DATATYPE}} nu este o solicitare validă pentru conținut brut.",
"invalid-request": "Adresa URL solicitată \"{{{url}}}\" nu este o solicitare validă.",
"no-value-for-arg": "Nu este furnizată nicio valoare pentru argumentul {{ARGUMENT}}",
"no-query": "Nu a fost furnizată nicio interogare.",
"raw-entry-not-found": "Nu se poate găsi {{DATATYPE}} intrarea {{ENTRY}}",
"400-page-title": "Cerere invalidă",
"400-page-heading": "Cerere invalidă",
"404-page-title": "Conținut nu a fost găsit",
"404-page-heading": "Nu a fost găsit",
"500-page-title": "Eroare internă de server",
"500-page-heading": "Eroare internă de server",
"500-page-text": "A apărut o eroare internă de server. Ne pare rau pentru asta :/",
"external-link-intro": "Ești pe cale să părăsești cititorul ZIM al Kiwix pentru a te conecta la...",
"fulltext-search-unavailable": "Căutarea text integral indisponibilă",
"no-search-results": "Motorul de căutare textintegral nu este disponibil pentru acest conținut.",
"search-results-page-title": "Căutare: {{SEARCH_PATTERN}}",
"search-results-page-header": "Rezultatele <b>{{START}}-{{END}}</b> din <b>{{COUNT}}</b> pentru <b>„{{{SEARCH_PATTERN}}}”</b>",
"empty-search-results-page-header": "Nu au fost găsite rezultate pentru <b>„{{{SEARCH_PATTERN}}}”</b>",
"search-result-book-info": "din {{BOOK_TITLE}}",
"word-count": "{{COUNT}} cuvinte",
"library-button-text": "Mergi la pagina de pornire",
"home-button-text": "Accesați pagina principală a „{{BOOK_TITLE}}”",
"random-page-button-text": "Accesați o pagină selectată aleatoriu",
"searchbox-tooltip": "Căutați „{{BOOK_TITLE}}”",
"confusion-of-tongues": "Două sau mai multe cărți în limbi diferite ar participa la căutare, ceea ce poate duce la rezultate confuze.",
"welcome-page-overzealous-filter": "Nici un rezultat. Doriți să <a href=\"{{URL}}\">resetați filtrul</a>?",
"powered-by-kiwix-html": "Susținut de&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Caută",
"book-filtering-all-categories": "Toate categoriile",
"book-filtering-all-languages": "Toate limbile",
"count-of-matching-books": "{{COUNT}} cărți",
"download": "Descărcare",
"direct-download-link-text": "Direct",
"direct-download-alt-text": "Descărcați direct prin HTTP(S)",
"hash-download-link-text": "Sumă de control SHA-256",
"hash-download-alt-text": "Afișează suma de verificare a fișierului SHA-256",
"magnet-link-text": "Legătură magnetică",
"magnet-alt-text": "Descărcați prin linkul Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Descărcați prin BitTorrent",
"library-opds-feed-all-entries": "Bibliotecă OPDS Feed - Toate intrările",
"filter-by-tag": "Filtrați după eticheta \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Opriți filtrarea după eticheta \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "Bibliotecă OPDS Feed - intrări care se potrivesc cu {{#LANG}}\nLimba: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtichetă: {{TAG}} {{/TAG}}{{#Q}}\nInterogare: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bun venit la Kiwix Server",
"download-links-heading": "Descărcați linkuri pentru <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Descărcă cartea",
"preview-book": "Previzualizare",
"unknown-error": "Eroare necunoscută",
"book-category.wikibooks": "Wikimanuale",
"book-category.wikinews": "Wikiștiri",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikicitat",
"book-category.wikisource": "Wikisursă",
"book-category.wikispecies": "Wikispecii",
"book-category.wikiversity": "Wikiversitate",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wikționar",
"book-category.other": "Altul"
}

View File

@@ -38,16 +38,16 @@
"count-of-matching-books": "{{COUNT}} libru/os",
"download": "Iscàrriga",
"direct-download-link-text": "Diretu",
"direct-download-alt-text": "Iscàrriga in manera direta tràmite HTTP(S)",
"hash-download-link-text": "Summa de controllu SHA-256",
"hash-download-alt-text": "Mustra sa summa de controllu SHA-256",
"direct-download-alt-text": "iscarrigamentu diretu",
"hash-download-link-text": "Hash SHA256",
"hash-download-alt-text": "hash de s'iscarrigamentu",
"magnet-link-text": "Ligàmene Magnet",
"magnet-alt-text": "Iscàrriga impreende su ligàmene Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Iscàrriga impreende BitTorrent",
"magnet-alt-text": "ligàmene \"magnet\" de iscarrigamentu",
"torrent-download-link-text": "Documentu Torrent",
"torrent-download-alt-text": "iscàrriga su torrent",
"library-opds-feed-all-entries": "Flussu OPDS de sa biblioteca Totu sos elementos",
"filter-by-tag": "Filtra pro eticheta \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Non filtres prus pro eticheta \"{{{TAG}}}\"",
"filter-by-tag": "Filtra pro eticheta \"{{TAG}}\"",
"stop-filtering-by-tag": "Non filtres prus pro eticheta \"{{TAG}}\"",
"library-opds-feed-parameterised": "Flussu OPDS de sa biblioteca - elementos chi currispondet cun {{#LANG}}\nLimba: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEticheta: {{TAG}} {{/TAG}}{{#Q}}\nChirca: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bene bènnidu a su serbidore de Kiwix",
"download-links-heading": "Ligàmenes de iscarrigamentu pro <b><i>{{BOOK_TITLE}}</i></b>",

View File

@@ -9,8 +9,6 @@
"400-page-heading": "غلط ارداس",
"404-page-title": "مواد کائنی لبھیا",
"404-page-heading": "کائنی لبھا",
"new-404-page-title": "ورقہ کائنی لبھیا",
"404-img-text": "کائنی لبھا!",
"500-page-title": "اندرلا سرور نقص",
"500-page-heading": "اندرلا سرور نقص",
"search": "ڳولو",

View File

@@ -10,7 +10,7 @@
"suggest-full-text-search": "vsebuje »{{{SEARCH_TERMS}}}« ...",
"no-such-book": "Ni take knjige: {{BOOK_NAME}}",
"too-many-books": "Preveč zahtevanih knjig ({{NB_BOOKS}}), omejitev je {{LIMIT}}",
"no-book-found": "Izbranim merilom ne ustreza nobena knjiga",
"no-book-found": "Izbirnim merilom ne ustreza nobena knjiga",
"url-not-found": "Zahtevanega URL-ja »{{url}}« v tem strežniku ni bilo mogoče najti.",
"suggest-search": "Preiščite celotno besedilo za <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Ni bilo mogoče izbrati naključnega članka :(",
@@ -23,25 +23,9 @@
"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": "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.",
"500-page-heading": "Notranja napaka strežnika",
"500-page-text": "Prišlo je do notranje napake strežnika. Žal nam je za to. :/",
"fulltext-search-unavailable": "Iskanje po celotnem besedilu ni na voljo",
"no-search-results": "Iskalnik po celotnem besedilu za to vsebino ni na voljo.",
"search-results-page-title": "Iskanje: {{SEARCH_PATTERN}}",
@@ -49,10 +33,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": "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}}}«",
"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}}«",
"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>",
@@ -62,31 +46,20 @@
"count-of-matching-books": "{{COUNT}} knjiga(i/e)",
"download": "Prenesi",
"direct-download-link-text": "Neposredno",
"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",
"direct-download-alt-text": "neposredni prenos",
"hash-download-link-text": "Zgoščena vrednost SHA256",
"hash-download-alt-text": "prenesi zgoščeno vrednost",
"magnet-link-text": "Magnetna povezava",
"magnet-alt-text": "Prenos prek magnetne povezave",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Prenos prek BitTorrenta",
"magnet-alt-text": "prenesi magnet",
"torrent-download-link-text": "Torrent datoteka",
"torrent-download-alt-text": "prenesi torrent",
"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",
"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"
"unknown-error": "Neznana napaka"
}

View File

@@ -13,7 +13,6 @@
"suggest-search": "Bëni një kërkim të plotë teksti për <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oh! Su arrit të merrej një artikull kuturu :(",
"invalid-raw-data-type": "{{DATATYPE}} sështë varg i vlefshëm kërkimi për lëndë të papërpunuar.",
"invalid-request": "URL-ja e kërkuar “{{{url}}}” sështë kërkesë e vlefshme.",
"no-value-for-arg": "Su dha vlerë për argumentin {{ARGUMENT}}",
"no-query": "Su dha varg kërkimi.",
"raw-entry-not-found": "Sgjendet dot zëri {{DATATYPE}} {{ENTRY}}",
@@ -23,14 +22,8 @@
"404-page-heading": "Su Gjet",
"500-page-title": "Gabim i Brendshëm Shërbyesi",
"500-page-heading": "Gabim i Brendshëm Shërbyesi",
"500-page-text": "Ndodhi një gabim i brendshëm shërbyesi. Na ndjeni për këtë :/",
"fulltext-search-unavailable": "Kërkim teksti të plotë jo i mundshëm",
"no-search-results": "Sështë i passhëm motori i kërkimit të tekstit të plotë për këtë lëndë.",
"search-results-page-title": "Kërkim: {{SEARCH_PATTERN}}",
"search-results-page-header": "Përfundime <b>{{START}}-{{END}}</b> nga <b>{{COUNT}}</b> gjithsej për <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Su gjetën përfundime për <b>“{{{SEARCH_PATTERN}}}”</b>",
"search-result-book-info": "nga {{BOOK_TITLE}}",
"word-count": "{{COUNT}} fjalë",
"library-button-text": "Kalo te faqja e mirëseardhjes",
"home-button-text": "Kalo te faqja krye e '{{BOOK_TITLE}}'",
"random-page-button-text": "Kalo te një faqe e përzgjedhur kuturu",
@@ -44,30 +37,19 @@
"count-of-matching-books": "{{COUNT}} libër(a)",
"download": "Shkarkoje",
"direct-download-link-text": "Drejtpërsëdrejti",
"direct-download-alt-text": "Shkarkoje drejtpërdrejti përmes HTTP(S)",
"hash-download-link-text": "“Checksum” SHA-256",
"hash-download-alt-text": "Shfaq “checksum” SHA-256 kartele",
"direct-download-alt-text": "shkarkim i drejtpërdrejt",
"hash-download-link-text": "Hash sha256",
"hash-download-alt-text": "shkarko hashin",
"magnet-link-text": "Lidhje Magnet",
"magnet-alt-text": "Shkarkoje përmes lidhjeje Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Shkarkoje përmes BitTorrent-it",
"magnet-alt-text": "shkarko magnetin",
"torrent-download-link-text": "Kartelë Torrent",
"torrent-download-alt-text": "shkarko torrent-in",
"library-opds-feed-all-entries": "Prurje OPDS Biblioteke - Krejt zërat",
"filter-by-tag": "Filtroji sipas etikete “{{{TAG}}}”",
"stop-filtering-by-tag": "Resht së filtruari sipas etikete “{{{TAG}}}”",
"filter-by-tag": "Filtroji sipas etiketës “{{TAG}}”",
"stop-filtering-by-tag": "Resht së filtruari sipas etiketë “{{TAG}}”",
"library-opds-feed-parameterised": "Prurje OPDS Biblioteke - zëra që kanë përputhje me {{#LANG}}\nGjuhë: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtiketë: {{TAG}} {{/TAG}}{{#Q}}\nVarg Kërkimi: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Mirë se vini në Shërbyesin Kiwix",
"download-links-heading": "Lidhje shkarkimi për <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Shkarkoje librin",
"preview-book": "Paraparje",
"unknown-error": "Gabim i panjohur",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionary",
"book-category.other": "Tjetër"
"preview-book": "Paraparje"
}

View File

@@ -1,9 +1,7 @@
{
"@metadata": {
"authors": [
"Apq",
"Jopparn",
"Larsa",
"Rofiatmustapha12",
"Sabelöga",
"WikiPhoenix"
@@ -26,25 +24,9 @@
"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": "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.",
"500-page-heading": "Internt serverfel",
"500-page-text": "Ett internt serverfel uppstod. Vi ber om ursäkt för det :/",
"fulltext-search-unavailable": "Fulltextsökning är inte tillgänglig",
"no-search-results": "Sökmaskinen för fulltext är inte tillgänglig för detta innehåll.",
"search-results-page-title": "Sök: {{SEARCH_PATTERN}}",
@@ -53,9 +35,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 '{{{BOOK_TITLE}}}'",
"searchbox-tooltip": "Sök efter \"{{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&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
@@ -73,23 +55,12 @@
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Ladda ner via BitTorrent",
"library-opds-feed-all-entries": "Library OPDS Feed - Alla poster",
"filter-by-tag": "Filtrera efter taggen \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Sluta filtrera efter taggen \"{{{TAG}}}\"",
"filter-by-tag": "Filtrera efter taggen \"{{TAG}}\"",
"stop-filtering-by-tag": "Sluta filtrera efter taggen \"{{TAG}}\"",
"library-opds-feed-parameterised": "Library OPDS Feed - poster som matchar {{#LANG}}\nSpråk: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTagg: {{TAG}} {{/TAG}}{{#Q}}\nFråga: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Välkommen till Kiwix Server",
"download-links-heading": "Nedladdningslänkar för <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Ladda ned bok",
"preview-book": "Förhandsgranska",
"unknown-error": "Okänt fel",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionary",
"book-category.other": "Övriga",
"text-loading-content": "Laddar innehåll"
"unknown-error": "Okänt fel"
}

View File

@@ -1,8 +1,6 @@
{
"@metadata": {
"authors": [
"Joeamj",
"Muddyb",
"Peggy",
"Wangombe"
]
@@ -42,7 +40,7 @@
"welcome-page-overzealous-filter": "Hakuna matokeo. Je, ungependa <a href=\"{{URL}}\">kuweka upya kichujio</a> ?",
"powered-by-kiwix-html": "Inaendeshwa na <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Tafuta",
"book-filtering-all-categories": "Jamii Zote",
"book-filtering-all-categories": "Kategoria Zote",
"book-filtering-all-languages": "Lugha zote",
"count-of-matching-books": "Vitabu {{COUNT}}",
"download": "Pakua",
@@ -62,6 +60,5 @@
"download-links-heading": "Pakua viungo vya <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Pakua vitabu",
"preview-book": "Hakiki",
"unknown-error": "Hitilafu isiyojulikana",
"text-loading-content": "Inapakia Maudhui"
"unknown-error": "Hitilafu isiyojulikana"
}

View File

@@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Amire80",
"Chaduvari",
"Rishitha 1238",
"V Bhavya"

View File

@@ -13,25 +13,6 @@
, "400-page-heading": "[I18N TESTING] -400 karma for an invalid request"
, "404-page-title": "[I18N TESTING] Not Found - Try Again"
, "404-page-heading": "[I18N TESTING] Content not found, but at least the server is alive"
, "new-404-page-title" : "Page [I18N] not [TESTING] found"
, "new-404-page-heading" : "[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave."
, "404-img-text": "[I18N] Not found! [TESTING]"
, "path-was-not-found": "[I18N TESTING] The requested path was not found (in fact, nothing was found instead, either):"
, "404-advice.p1": "Sh*t happens. [I18N TESTING] Take it easy!"
, "404-advice.p2": "[I18N TESTING] Try one of the following:"
, "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"
, "external-link-advice.p1": "[I18N TESTING] The link you're trying to access points past the end of the ZIM file."
, "external-link-advice.p2": "[I18N TESTING] If you are an optimist, you can attempt to open the link."
, "external-link-advice.p3": "[I18N TESTING] But we strongly recommend you to be reasonable and press your browser's back button."
, "library-button-text": "[I18N TESTING] Navigate to the welcome page"
, "home-button-text": "[I18N TESTING] Jump to the main page of '{{BOOK_TITLE}}'"
, "random-page-button-text": "[I18N TESTING] I am tired of determinism"
@@ -84,5 +65,4 @@
, "book-category.wikivoyage": "[I18N] Wikivoyage [TESTING]"
, "book-category.wiktionary": "[I18N] Wiktionary [TESTING]"
, "book-category.other": "[I18N] Other [TESTING]"
, "text-loading-content": "[I18N TESTING] Loading content"
}

View File

@@ -2,8 +2,7 @@
"@metadata": {
"authors": [
"Hedda",
"Rofiatmustapha12",
"SaldırganSincap"
"Rofiatmustapha12"
]
},
"name": "Türkçe",
@@ -24,8 +23,8 @@
"404-page-title": "içerik bulunamadı",
"404-page-heading": "Bulunamadı",
"500-page-title": "İç Sunucu Hatası",
"500-page-heading": "Üzgünüz. Sayfa çalışmıyor.",
"500-page-text": "İstenen yol düzgün bir şekilde teslim edilemiyor:",
"500-page-heading": "İç Sunucu Hatası",
"500-page-text": "Dahili bir sunucu hatası oluştu. Bunun için üzgünüz :/",
"fulltext-search-unavailable": "Tam metin araması kullanılamıyor",
"no-search-results": "Tam metin arama motoru bu içerik için kullanılamaz.",
"search-results-page-title": "Arama: {{SEARCH_PATTERN}}",
@@ -34,9 +33,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}}}' ana sayfasına git",
"home-button-text": "'{{BOOK_TITLE}}' anasayfasına gidin",
"random-page-button-text": "Rastgele seçilen bir sayfaya git",
"searchbox-tooltip": "'{{{BOOK_TITLE}}}' ara",
"searchbox-tooltip": "'{{BOOK_TITLE}}' ara",
"confusion-of-tongues": "Aramaya farklı dillerde iki veya daha fazla kitap katılacak ve bu da kafa karıştırıcı sonuçlara yol açabilecektir.",
"welcome-page-overzealous-filter": "Sonuç yok. <a href=\"{{URL}}\">Filtreyi sıfırlamak</a> ister misiniz?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> tarafından desteklenmektedir",
@@ -46,16 +45,16 @@
"count-of-matching-books": "{{COUNT}} kitap",
"download": "İndir",
"direct-download-link-text": "Doğrudan",
"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",
"direct-download-alt-text": "direkt indirme",
"hash-download-link-text": "Sha256 haşesi",
"hash-download-alt-text": "csv indir",
"magnet-link-text": "Mıknatıs bağlantısı",
"magnet-alt-text": "Mıknatıs bağlantısıyla indir",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "BitTorrent üzerinden indir",
"magnet-alt-text": "mıknatısı indir",
"torrent-download-link-text": "Hedef dosya",
"torrent-download-alt-text": "torrenti indir",
"library-opds-feed-all-entries": "Kütüphane OPDS Akışı - Tüm girişler",
"filter-by-tag": "\"{{{TAG}}}\" etiketine göre filtrele",
"stop-filtering-by-tag": "\"{{{TAG}}}\" etiketine göre filtrelemeyi durdur",
"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ı",

View File

@@ -1,12 +1,9 @@
{
"@metadata": {
"authors": [
"Cyanjiang",
"GuoPC",
"IceButBin",
"Kichin",
"Peterxy12",
"Prmsh",
"StarrySky",
"Sunai",
"XtexChooser",
@@ -20,7 +17,7 @@
"no-book-found": "没有符合搜索要求的图书",
"url-not-found": "在此服务器上找不到请求的 URL{{url}}",
"suggest-search": "对<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>进行全文搜索",
"random-article-failure": "抱歉! 随机条目失败了 (⁠〒⁠﹏⁠〒⁠)",
"random-article-failure": "抱歉! 随机条目失败了 (⁠〒⁠﹏⁠〒⁠) 【好生草的表情(】",
"invalid-raw-data-type": "{{DATATYPE}} 对原请求无效。",
"invalid-request": "请求的URL无效{{{url}}}",
"no-value-for-arg": "参数{{ARGUMENT}}无值",
@@ -30,25 +27,9 @@
"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-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 的离线内容。",
"500-page-heading": "内部服务器错误",
"500-page-text": "内部服务器出现错误。真的十分抱歉 (;⁠ŏ⁠﹏⁠ŏ~)",
"fulltext-search-unavailable": "全文搜索不可用",
"no-search-results": "全文搜索引擎不适用于该内容。",
"search-results-page-title": "搜索:{{SEARCH_PATTERN}}",
@@ -57,9 +38,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>提供技术支持",
@@ -94,6 +75,5 @@
"book-category.wikiversity": "维基学院",
"book-category.wikivoyage": "维基导游",
"book-category.wiktionary": "维基词典",
"book-category.other": "其他",
"text-loading-content": "内容加载中"
"book-category.other": "其他"
}

View File

@@ -23,25 +23,9 @@
"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-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 離線內容。",
"500-page-heading": "內部伺服器錯誤",
"500-page-text": "內部伺服器發生錯誤。對此我們深感抱歉:/",
"fulltext-search-unavailable": "全文搜尋無效",
"no-search-results": "全文搜尋引擎不適用此內容。",
"search-results-page-title": "搜尋:{{SEARCH_PATTERN}}",
@@ -50,9 +34,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> 提供技術支援",
@@ -69,10 +53,10 @@
"magnet-alt-text": "透過磁力連結下載",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "透過 BitTorrent 下載",
"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-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}}",
"welcome-to-kiwix-server": "歡迎來到 Kiwix 伺服器",
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
"download-links-title": "下載書籍",
@@ -95,6 +79,5 @@
"book-category.wikiversity": "維基學院",
"book-category.wikivoyage": "維基導遊",
"book-category.wiktionary": "維基詞典",
"book-category.other": "其他",
"text-loading-content": "正在載入內容"
"book-category.other": "其他"
}

View File

@@ -152,21 +152,8 @@
: '';
}
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) {
@@ -196,6 +183,9 @@
} catch {
downloadLink = '';
}
const bookName = link.split('/').pop();
const viewerLink = `${root}/viewer#${bookName}`;
const humanFriendlyZimSize = humanFriendlySize(zimSize);
const divTag = document.createElement('div');
@@ -207,7 +197,9 @@
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
let bookLinkWrapper = `
divTag.innerHTML = `
<div class="book__wrapper">
<a class="book__link" href="${viewerLink}" data-hover="Preview">
<div class="book__link__wrapper">
<div class="book__icon" ${faviconAttr}></div>
<div class="book__header">
@@ -215,11 +207,7 @@
</div>
<div class="book__description" title="${description}">${description}</div>
</div>
`;
divTag.innerHTML = `
<div class="book__wrapper">
${addBookPreviewLink(bookLinkWrapper, book)}
</a>
<div class="book__meta">
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
@@ -250,8 +238,7 @@
let finalValue = (keysToURIEncode.indexOf(key) >= 0) ? encodeURIComponent(value) : value;
output += `&${key}=${finalValue}`;
}
// exclude first char so the first params are not prefixed with &
return output.substring(1);
return output;
}
/* hack for library.kiwix.org magnet links (created by MirrorBrain)

View File

@@ -13,36 +13,6 @@ html {
body {
position: relative;
box-sizing: border-box;
background: #ffffff;
}
.loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
background: rgba(255, 255, 255, 0.8);
padding: 10px 20px;
border-radius: 5px;
display: flex;
align-items: center;
gap: 10px;
white-space: nowrap; /* Prevents text from wrapping */
min-width: fit-content; /* Ensures the container doesnt shrink */
}
.spinner {
width: 20px;
height: 20px;
border: 3px solid #ccc;
border-top: 3px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.hidden {
visibility: hidden;
}
::selection {
@@ -102,10 +72,6 @@ body {
padding: 20px;
}
#content_iframe {
background: #ffffff;
}
#uiLanguageSelector {
display: none;
}
@@ -130,11 +96,6 @@ body {
height: 30px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@font-face {
font-family: "poppins";
src: url("../skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");

View File

@@ -2,12 +2,12 @@ const uiLanguages = [
{
"iso_code": "ar",
"self_name": "الإنجليزية",
"translation_count": 44
"translation_count": 25
},
{
"iso_code": "bn",
"self_name": "বাংলা",
"translation_count": 34
"translation_count": 14
},
{
"iso_code": "br",
@@ -27,7 +27,7 @@ const uiLanguages = [
{
"iso_code": "de",
"self_name": "Deutsch",
"translation_count": 67
"translation_count": 57
},
{
"iso_code": "el",
@@ -37,12 +37,12 @@ const uiLanguages = [
{
"iso_code": "en",
"self_name": "English",
"translation_count": 93
"translation_count": 58
},
{
"iso_code": "es",
"self_name": "español",
"translation_count": 67
"translation_count": 49
},
{
"iso_code": "fi",
@@ -52,7 +52,7 @@ const uiLanguages = [
{
"iso_code": "fr",
"self_name": "Français",
"translation_count": 84
"translation_count": 57
},
{
"iso_code": "ha",
@@ -62,32 +62,22 @@ const uiLanguages = [
{
"iso_code": "he",
"self_name": "עברית",
"translation_count": 80
"translation_count": 57
},
{
"iso_code": "hi",
"self_name": "हिन्दी",
"translation_count": 59
},
{
"iso_code": "hu",
"self_name": "Magyar",
"translation_count": 32
"translation_count": 49
},
{
"iso_code": "hy",
"self_name": "Հայերեն",
"translation_count": 26
"translation_count": 15
},
{
"iso_code": "ia",
"self_name": "interlingua",
"translation_count": 84
},
{
"iso_code": "id",
"self_name": "Bahasa Inggris",
"translation_count": 68
"translation_count": 57
},
{
"iso_code": "ig",
@@ -97,7 +87,7 @@ const uiLanguages = [
{
"iso_code": "it",
"self_name": "italiano",
"translation_count": 58
"translation_count": 38
},
{
"iso_code": "ja",
@@ -107,7 +97,7 @@ const uiLanguages = [
{
"iso_code": "ko",
"self_name": "한국어",
"translation_count": 73
"translation_count": 15
},
{
"iso_code": "ku-latn",
@@ -117,27 +107,22 @@ const uiLanguages = [
{
"iso_code": "lb",
"self_name": "Lëtzebuergesch",
"translation_count": 48
"translation_count": 22
},
{
"iso_code": "mk",
"self_name": "македонски",
"translation_count": 81
"translation_count": 57
},
{
"iso_code": "ms",
"self_name": "Bahasa Melayu",
"translation_count": 14
},
{
"iso_code": "nb",
"self_name": "Engelsk",
"translation_count": 68
},
{
"iso_code": "nl",
"self_name": "Nederlands",
"translation_count": 68
"translation_count": 49
},
{
"iso_code": "nqo",
@@ -152,27 +137,17 @@ const uiLanguages = [
{
"iso_code": "pl",
"self_name": "Polski",
"translation_count": 32
"translation_count": 31
},
{
"iso_code": "pt-br",
"self_name": "Português",
"translation_count": 66
},
{
"iso_code": "pt",
"self_name": "português",
"translation_count": 67
},
{
"iso_code": "ro",
"self_name": "Engleză",
"translation_count": 68
"translation_count": 35
},
{
"iso_code": "ru",
"self_name": "русский",
"translation_count": 67
"translation_count": 57
},
{
"iso_code": "sc",
@@ -187,7 +162,7 @@ const uiLanguages = [
{
"iso_code": "skr-arab",
"self_name": "سرائیکی",
"translation_count": 33
"translation_count": 20
},
{
"iso_code": "sl",
@@ -197,17 +172,17 @@ const uiLanguages = [
{
"iso_code": "sq",
"self_name": "Shqip",
"translation_count": 67
"translation_count": 49
},
{
"iso_code": "sv",
"self_name": "Svenska",
"translation_count": 67
"translation_count": 57
},
{
"iso_code": "sw",
"self_name": "Kiswahili",
"translation_count": 58
"translation_count": 57
},
{
"iso_code": "te",
@@ -222,11 +197,11 @@ const uiLanguages = [
{
"iso_code": "zh-hans",
"self_name": "简体中文",
"translation_count": 68
"translation_count": 57
},
{
"iso_code": "zh-hant",
"self_name": "繁體中文",
"translation_count": 92
"translation_count": 57
}
]

View File

@@ -1,3 +0,0 @@
#kiwixtoolbarwrapper {
display: none !important;
}

View File

@@ -34,12 +34,6 @@
width: 20em;
}
#kiwixsearchbox {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
#kiwix_serve_taskbar_home_button button {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -10,8 +10,6 @@ let viewerState = {
uiLanguage: 'en',
};
const FULL_ROOT_URL = `${window.location.origin}${root}`;
function dropUserLang(query) {
const q = new URLSearchParams(query);
q.delete('userlang');
@@ -94,10 +92,7 @@ function makeJSLink(jsCodeString, linkText, linkAttr="") {
// in the JS code we have to URI-encode a second time.
// (see https://stackoverflow.com/questions/33721510)
const uriEncodedJSCode = encodeURIComponent(jsCodeString);
const linkPlainText = htmlDecode(linkText, "text/html");
linkAttr += ` href="javascript:${uriEncodedJSCode}"`;
linkAttr += ` title="${linkPlainText}"`;
return `<a ${linkAttr}>${linkText}</a>`;
return `<a ${linkAttr} href="javascript:${uriEncodedJSCode}">${linkText}</a>`;
}
function suggestionsApiURL()
@@ -264,23 +259,29 @@ function handle_location_hash_change() {
}
function translateErrorPageIfNeeded() {
translatePageInWindow(contentIframe.contentWindow);
const cw = contentIframe.contentWindow;
if ( cw.KIWIX_RESPONSE_TEMPLATE && cw.KIWIX_RESPONSE_DATA ) {
const template = htmlDecode(cw.KIWIX_RESPONSE_TEMPLATE);
// cw.KIWIX_RESPONSE_DATA belongs to the iframe context and running
// I18n.render() on it directly in the top context doesn't work correctly
// because the type checks (obj.__proto__ == ???.prototype) in
// I18n.instantiateParameterizedMessages() always fail (String.prototype
// refers to different objects in different contexts).
// Work arround that issue by copying the object into our context.
const params = JSON.parse(JSON.stringify(cw.KIWIX_RESPONSE_DATA));
const html = I18n.render(template, params);
const htmlDoc = new DOMParser().parseFromString(html, "text/html");
cw.document.documentElement.innerHTML = htmlDoc.documentElement.innerHTML;
}
}
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 + iframeLocation.hash;
const iframeContentUrl = iframeLocation.pathname;
const iframeContentQuery = iframeLocation.search;
const newHash = iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery);
history.replaceState(viewerState, null, makeURL(location.search, newHash));
@@ -341,38 +342,20 @@ function setHrefAvoidingWombatRewriting(target, url) {
target._no_rewrite = old_no_rewrite;
}
function linkShouldBeOpenedInANewWindow(linkElement, mouseEvent) {
return linkElement.getAttribute("target") == "_blank"
|| mouseEvent.ctrlKey
|| mouseEvent.shiftKey
|| mouseEvent.metaKey /* on Macs */;
}
function goingToOpenALinkToAnUndisplayableResource(url) {
return !navigator.pdfViewerEnabled && url.pathname.endsWith('.pdf');
}
function onClickEvent(e) {
const iframeDocument = contentIframe.contentDocument;
const target = matchingAncestorElement(e.target, iframeDocument, "a");
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) ) {
if ( isExternalAppUrl && !viewerSettings.linkBlockingEnabled ) {
target.setAttribute("target", "_blank");
}
if (isExternalAppUrl || isExternalUrl(target_href)) {
const possiblyBlockedLink = blockLink(target_href);
if ( linkShouldBeOpenedInANewWindow(target, e) ) {
if ( e.ctrlKey || e.shiftKey ) {
// The link will be loaded in a new tab/window - update the link
// and let the browser handle the rest.
setHrefAvoidingWombatRewriting(target, possiblyBlockedLink);
@@ -415,32 +398,21 @@ this.Element && function(ElementPrototype) {
}
}(Element.prototype);
function setup_external_link_blocker() {
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
}
////////////////////////////////////////////////////////////////////////////////
// End of external link blocking
////////////////////////////////////////////////////////////////////////////////
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}`;
}
}
}
let viewerSetupComplete = false;
function on_content_load() {
const loader = document.getElementById("kiwix__loader");
contentIframe.classList.remove("hidden");
loader.style.display = "none";
contentIframe.contentWindow.onhashchange = handle_content_url_change;
setInterval(handle_content_url_change, 100);
setup_chaperon_mode();
if ( viewerSetupComplete ) {
handle_content_url_change();
}
setup_external_link_blocker();
}
function htmlDecode(input) {
@@ -588,7 +560,6 @@ function setupViewer() {
const kiwixToolBarWrapper = document.getElementById('kiwixtoolbarwrapper');
if ( ! viewerSettings.toolbarEnabled ) {
finishViewerSetup();
return;
}
@@ -626,24 +597,15 @@ function updateUIText() {
setTitle(document.getElementById("kiwix_serve_taskbar_random_button"),
$t("random-page-button-text"));
// Add translation for loading text as soon as translations are available
const loadingTextElement = document.getElementById("kiwix__loading_text");
if (loadingTextElement) {
loadingTextElement.textContent = $t("text-loading-content");
}
}
function finishViewerSetupOnceTranslationsAreLoaded()
{
updateUIText();
finishViewerSetup();
}
function finishViewerSetup()
{
handle_location_hash_change();
window.onhashchange = handle_location_hash_change;
window.onpopstate = handle_history_state_change;
viewerSetupComplete = true;
}

View File

@@ -1,32 +1,14 @@
<!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>{{external_link_detected}}</title>
<link type="text/css" href="{{root}}/skin/error.css?KIWIXCACHEID" rel="Stylesheet" />
<script type="module" src="{{root}}/skin/i18n.js?KIWIXCACHEID"></script>
<script>
window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";
window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};
</script>
</head>
<body>
<header>
<img src="{{root}}/skin/blocklink.svg?KIWIXCACHEID"
alt="{{caution_warning}}"
aria-label="{{caution_warning}}"
title="{{caution_warning}}">
</header>
<section class="intro">
<h1>{{external_link_detected}}</h1>
<p>{{external_link_intro}}</p>
<p><a href="{{url}}">{{ url }}</a></p>
</section>
<section class="advice">
<p>{{advice.p1}}</p>
<p>{{advice.p2}}</p>
<p>{{advice.p3}}</p>
</section>
</body>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<title>External link blocked</title>
</head>
<body class="kiwix">
<h1>External link blocked</h1>
<p>This instance of Kiwix protects you from accidentally going to external (out-of ZIM) links.</p>
<p>If you intend to go to such locations, please click the link below.</p>
<p><a href="{{ source }}">Go to {{ source }}</a></p>
<div id="kiwixfooter">Powered by <a href="https://kiwix.org">Kiwix</a></div>
</body>
</html>

View File

@@ -13,8 +13,7 @@
{{#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}}{{#contentAccessUrl}}<link type="text/html" href="{{contentAccessUrl}}/{{{content_id}}}" />
{{/contentAccessUrl}}
{{/icons}}<link type="text/html" href="{{root}}/content/{{{content_id}}}" />
<author>
<name>{{author_name}}</name>
</author>

View File

@@ -33,6 +33,7 @@
<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>

View File

@@ -3,6 +3,7 @@
<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"
@@ -107,7 +108,7 @@
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
{{#books}}
<div class="book__wrapper">
{{#contentAccessUrl}}<a class="book__link" href="{{contentAccessUrl}}/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">{{/contentAccessUrl}}
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
<div class="book__link__wrapper">
<div class="book__icon" {{faviconAttr}}></div>
<div class="book__header">
@@ -115,9 +116,9 @@
</div>
<div class="book__description" title="{{description}}">{{description}}</div>
</div>
{{#contentAccessUrl}}</a>{{/contentAccessUrl}}
</a>
<div class="book__meta">
<div class="book__languageTag" title="{{langTag.langFullString}}" aria-label="{{langTag.langFullString}}">{{langTag.langShortString}}</div>
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
<div class="book__tags"><div class="book__tags--wrapper">
{{#tagList}}
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>

View File

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

View File

@@ -21,7 +21,7 @@
<title>{{title}}</title>
<link>{{absolutePath}}</link>
{{#snippet}}
<description>{{{snippet}}}...</description>
<description>{{>snippet}}...</description>
{{/snippet}}
{{#bookTitle}}
<book>

View File

@@ -1,35 +0,0 @@
<!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" />
{{#KIWIX_RESPONSE_DATA}} <script>
window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";
window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};
</script>{{/KIWIX_RESPONSE_DATA}}
</head>
<body>
<header>
<img src="{{root}}/skin/404.svg?KIWIXCACHEID"
alt="{{404_img_text}}"
aria-label="{{404_img_text}}"
title="{{404_img_text}}">
</header>
<section class="intro">
<h1>{{PAGE_HEADING}}</h1>
<p>{{path_was_not_found_msg}}</p>
<p><code>{{url_path}}</code></p>
</section>
<section class="advice">
<p>{{advice.p1}}</p>
<p class="list-intro">{{advice.p2}}</p>
<ul>
<li>{{advice.p3}}</li>
<li>{{advice.p4}}</li>
</ul>
<p>{{advice.p5}}</p>
</section>
</body>
</html>

View File

@@ -1,31 +0,0 @@
<!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>

View File

@@ -11,7 +11,6 @@
<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>
@@ -34,10 +33,6 @@
</head>
<body style="margin:0" onload="setupViewer()">
<div class="loader" id="kiwix__loader">
<div class="spinner"></div>
<span id="kiwix__loading_text">Loading content...</span>
</div>
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
<div id="kiwixtoolbar" class="ui-widget-header">
<div class="kiwix_centered">
@@ -83,7 +78,7 @@
</div>
</div>
<iframe id="content_iframe" class="hidden"
<iframe id="content_iframe"
referrerpolicy="no-referrer"
onload="on_content_load()"
src="./skin/blank.html?KIWIXCACHEID" title="ZIM content" width="100%"

View File

@@ -1,143 +0,0 @@
#!/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

View File

@@ -51,21 +51,4 @@
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>

View File

Binary file not shown.

View File

@@ -18,15 +18,9 @@ protected:
const int PORT = 8002;
protected:
void resetServer(ZimFileServer::Cfg cfg) {
void resetServer(ZimFileServer::Options options) {
zfs1_.reset();
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);
zfs1_.reset(new ZimFileServer(PORT, options, "./test/library.xml"));
}
void SetUp() override {
@@ -156,30 +150,6 @@ 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&apos;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");
@@ -590,15 +560,6 @@ 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"
@@ -641,16 +602,6 @@ 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>
@@ -727,49 +678,6 @@ 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)
{
{
@@ -1277,10 +1185,11 @@ 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=b4e29e64\"\n" \
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link\n" \
@@ -1342,7 +1251,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" </div>\n" \
" </a>\n" \
" <div class=\"book__meta\">\n" \
" <div class=\"book__languageTag\" title=\"français\" aria-label=\"français\">fra</div>\n" \
" <div class=\"book__languageTag\" >fra</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
@@ -1369,7 +1278,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" </div>\n" \
" </a>\n" \
" <div class=\"book__meta\">\n" \
" <div class=\"book__languageTag\" title=\"English\" aria-label=\"English\">eng</div>\n" \
" <div class=\"book__languageTag\" >eng</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
@@ -1396,7 +1305,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" </div>\n" \
" </a>\n" \
" <div class=\"book__meta\">\n" \
" <div class=\"book__languageTag\" title=\"русский,English\" aria-label=\"русский,English\">mul</div>\n" \
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
@@ -1411,32 +1320,6 @@ 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&apos;t lead to a catastrophe\">Testing that running kiwix-serve without access to ZIM files doesn&apos;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" \
@@ -1449,7 +1332,6 @@ 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" \
@@ -1458,7 +1340,6 @@ 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" \
@@ -1572,47 +1453,4 @@ 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

View File

@@ -88,7 +88,6 @@ TEST(Manager, reload)
manager.reload({ "./test/library.xml" });
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
"charlesray",
"inaccessiblezim",
"raycharles",
"raycharles_uncategorized"
}));
@@ -96,14 +95,12 @@ 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"
}));

View File

@@ -15,8 +15,7 @@ tests = [
'server_helper',
'lrucache',
'i18n',
'response',
'spelling_correction'
'response'
]
if build_machine.system() != 'windows'
@@ -43,7 +42,6 @@ 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',

View File

@@ -57,27 +57,25 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/autoComplete.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css?cacheid=f2d376c4" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/error.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/error.css?cacheid=b3fa90cf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=e9a10ac1" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" },
{ 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=4e232c58" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" },
{ 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=b4e29e64" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9" },
{ 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=42e90cb9" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=00e0fdf3" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=aca897b0" },
{ 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 +84,7 @@ const ResourceCollection resources200Compressible{
// TODO: implement cache management of i18n resources
//{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n/test.json?cacheid=unknown" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/languages.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=08955948" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=ee7d95b5" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/search" },
@@ -94,8 +92,6 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/v2/entries" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/v2/partial_entries" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catch/external?source=www.example.com" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/search?content=zimfile&pattern=a" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/suggest?content=zimfile&term=ray" },
@@ -110,16 +106,10 @@ 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" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/blank.html?cacheid=6b1fa032" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/blocklink.svg" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/blocklink.svg?cacheid=bd56b116" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/caret.png" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/caret.png?cacheid=22b942b4" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download.png" },
@@ -164,8 +154,6 @@ 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" },
@@ -184,6 +172,8 @@ const ResourceCollection resources200Uncompressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/v2/searchdescription.xml" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/v2/illustration/6f1d19d0-633f-087b-fb55-7ac324ff9baf?size=48" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catch/external?source=www.example.com" },
{ ZIM_CONTENT, "/ROOT%23%3F/content/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
{ ZIM_CONTENT, "/ROOT%23%3F/content/corner_cases%23%26/empty.html" },
@@ -290,7 +280,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
const std::vector<UrlAndExpectedResult> testData{
{
/* url */ "/ROOT%23%3F/",
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=b4e29e64"
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
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">
@@ -300,11 +290,11 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=b4e29e64"
<link rel="shortcut icon" href="/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314">
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=e9a10ac1" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=08955948" defer></script>
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=ee7d95b5" 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=4e232c58" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" defer></script>
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
)EXPECTEDRESULT"
@@ -331,14 +321,13 @@ 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=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" />
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
<link type="text/css" href="./skin/taskbar.css?cacheid=80d56607" rel="Stylesheet" />
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
<script type="module" src="./skin/i18n.js?cacheid=e9a10ac1" 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="module" src="./skin/i18n.js?cacheid=071abc9a" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=ee7d95b5" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=aca897b0" 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>
@@ -350,21 +339,6 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=b4e29e
/* url */ "/ROOT%23%3F/content/zimfile/A/index",
""
},
{
/* url */ "/ROOT%23%3F/content/invalid-book/whatever",
R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/error.css?cacheid=b3fa90cf" rel="Stylesheet" />
window.KIWIX_RESPONSE_TEMPLATE = "&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n &lt;head&gt;\n &lt;meta charset=&quot;utf-8&quot;&gt;\n &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&quot; /&gt;\n &lt;title&gt;{{PAGE_TITLE}}&lt;/title&gt;\n &lt;link type=&quot;text/css&quot; href=&quot;{{root}}/skin/error.css?cacheid=b3fa90cf&quot; rel=&quot;Stylesheet&quot; /&gt;\n{{#KIWIX_RESPONSE_DATA}} &lt;script&gt;\n window.KIWIX_RESPONSE_TEMPLATE = &quot;{{KIWIX_RESPONSE_TEMPLATE}}&quot;;\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n &lt;/script&gt;{{/KIWIX_RESPONSE_DATA}}\n &lt;/head&gt;\n &lt;body&gt;\n &lt;header&gt;\n &lt;img src=&quot;{{root}}/skin/404.svg?cacheid=b6d648af&quot;\n alt=&quot;{{404_img_text}}&quot;\n aria-label=&quot;{{404_img_text}}&quot;\n title=&quot;{{404_img_text}}&quot;&gt;\n &lt;/header&gt;\n &lt;section class=&quot;intro&quot;&gt;\n &lt;h1&gt;{{PAGE_HEADING}}&lt;/h1&gt;\n &lt;p&gt;{{path_was_not_found_msg}}&lt;/p&gt;\n &lt;p&gt;&lt;code&gt;{{url_path}}&lt;/code&gt;&lt;/p&gt;\n &lt;/section&gt;\n &lt;section class=&quot;advice&quot;&gt;\n &lt;p&gt;{{advice.p1}}&lt;/p&gt;\n &lt;p class=&quot;list-intro&quot;&gt;{{advice.p2}}&lt;/p&gt;\n &lt;ul&gt;\n &lt;li&gt;{{advice.p3}}&lt;/li&gt;\n &lt;li&gt;{{advice.p4}}&lt;/li&gt;\n &lt;/ul&gt;\n &lt;p&gt;{{advice.p5}}&lt;/p&gt;\n &lt;/section&gt;\n &lt;/body&gt;\n&lt;/html&gt;\n";
<img src="/ROOT%23%3F/skin/404.svg?cacheid=b6d648af"
)EXPECTEDRESULT"
},
{
/* url */ "/ROOT%23%3F/catch/external?source=https%3A%2F%2Fkiwix.org",
R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/error.css?cacheid=b3fa90cf" rel="Stylesheet" />
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=e9a10ac1"></script>
window.KIWIX_RESPONSE_TEMPLATE = "&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n &lt;head&gt;\n &lt;meta charset=&quot;utf-8&quot;&gt;\n &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&quot; /&gt;\n &lt;title&gt;{{external_link_detected}}&lt;/title&gt;\n &lt;link type=&quot;text/css&quot; href=&quot;{{root}}/skin/error.css?cacheid=b3fa90cf&quot; rel=&quot;Stylesheet&quot; /&gt;\n &lt;script type=&quot;module&quot; src=&quot;{{root}}/skin/i18n.js?cacheid=e9a10ac1&quot;&gt;&lt;/script&gt;\n &lt;script&gt;\n window.KIWIX_RESPONSE_TEMPLATE = &quot;{{KIWIX_RESPONSE_TEMPLATE}}&quot;;\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n &lt;/script&gt;\n &lt;/head&gt;\n &lt;body&gt;\n &lt;header&gt;\n &lt;img src=&quot;{{root}}/skin/blocklink.svg?cacheid=bd56b116&quot;\n alt=&quot;{{caution_warning}}&quot;\n aria-label=&quot;{{caution_warning}}&quot;\n title=&quot;{{caution_warning}}&quot;&gt;\n &lt;/header&gt;\n &lt;section class=&quot;intro&quot;&gt;\n &lt;h1&gt;{{external_link_detected}}&lt;/h1&gt;\n &lt;p&gt;{{external_link_intro}}&lt;/p&gt;\n &lt;p&gt;&lt;a href=&quot;{{url}}&quot;&gt;{{ url }}&lt;/a&gt;&lt;/p&gt;\n &lt;/section&gt;\n &lt;section class=&quot;advice&quot;&gt;\n &lt;p&gt;{{advice.p1}}&lt;/p&gt;\n &lt;p&gt;{{advice.p2}}&lt;/p&gt;\n &lt;p&gt;{{advice.p3}}&lt;/p&gt;\n &lt;/section&gt;\n &lt;/body&gt;\n&lt;/html&gt;\n";
<img src="/ROOT%23%3F/skin/blocklink.svg?cacheid=bd56b116"
)EXPECTEDRESULT"
},
{
// Searching in a ZIM file without a full-text index returns
// a page rendered from static/templates/no_search_result_html
@@ -844,6 +818,89 @@ TEST_F(ServerTest, Http404HtmlError)
</p>
)" },
{ /* url */ "/ROOT%23%3F/content/invalid-book/whatever",
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/invalid-book/whatever" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "whatever", "SEARCH_URL" : "/ROOT%23%3F/search?pattern=whatever" } } } ] })" &&
expected_body==R"(
<h1>Not Found</h1>
<p>
The requested URL "/ROOT%23%3F/content/invalid-book/whatever" was not found on this server.
</p>
<p>
Make a full text search for <a href="/ROOT%23%3F/search?pattern=whatever">whatever</a>
</p>
)" },
{ /* url */ "/ROOT%23%3F/content/zimfile/invalid-article",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/invalid-article" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "invalid-article", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=invalid-article" } } } ] })" &&
expected_body==R"(
<h1>Not Found</h1>
<p>
The requested URL "/ROOT%23%3F/content/zimfile/invalid-article" was not found on this server.
</p>
<p>
Make a full text search for <a href="/ROOT%23%3F/search?content=zimfile&pattern=invalid-article">invalid-article</a>
</p>
)" },
{ /* url */ R"(/ROOT%23%3F/content/"><svg onload=alert(1)>)",
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/\"><svg onload%3Dalert(1)>" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "\"><svg onload=alert(1)>", "SEARCH_URL" : "/ROOT%23%3F/search?pattern=%22%3E%3Csvg%20onload%3Dalert(1)%3E" } } } ] })" &&
expected_body==R"(
<h1>Not Found</h1>
<p>
The requested URL "/ROOT%23%3F/content/&quot;&gt;&lt;svg onload%3Dalert(1)&gt;" was not found on this server.
</p>
<p>
Make a full text search for <a href="/ROOT%23%3F/search?pattern=%22%3E%3Csvg%20onload%3Dalert(1)%3E">&quot;&gt;&lt;svg onload=alert(1)&gt;</a>
</p>
)" },
{ /* url */ R"(/ROOT%23%3F/content/zimfile/"><svg onload=alert(1)>)",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/\"><svg onload%3Dalert(1)>" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "\"><svg onload=alert(1)>", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=%22%3E%3Csvg%20onload%3Dalert(1)%3E" } } } ] })" &&
expected_body==R"(
<h1>Not Found</h1>
<p>
The requested URL "/ROOT%23%3F/content/zimfile/&quot;&gt;&lt;svg onload%3Dalert(1)&gt;" was not found on this server.
</p>
<p>
Make a full text search for <a href="/ROOT%23%3F/search?content=zimfile&pattern=%22%3E%3Csvg%20onload%3Dalert(1)%3E">&quot;&gt;&lt;svg onload=alert(1)&gt;</a>
</p>
)" },
// XXX: This test case is against a "</script>" string appearing inside
// XXX: javascript code that will confuse the HTML parser
{ /* url */ R"(/ROOT%23%3F/content/zimfile/</script>)",
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/</scr\ipt>" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "script>", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=script%3E" } } } ] })" &&
expected_body==R"(
<h1>Not Found</h1>
<p>
The requested URL "/ROOT%23%3F/content/zimfile/&lt;/script&gt;" was not found on this server.
</p>
<p>
Make a full text search for <a href="/ROOT%23%3F/search?content=zimfile&pattern=script%3E">script&gt;</a>
</p>
)" },
{ /* url */ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
expected_page_title=="[I18N TESTING] Not Found - Try Again" &&
book_name=="zimfile" &&
book_title=="Ray Charles" &&
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/content/zimfile/invalid-article" } } }, { "p" : { "msgid" : "suggest-search", "params" : { "PATTERN" : "invalid-article", "SEARCH_URL" : "/ROOT%23%3F/search?content=zimfile&pattern=invalid-article" } } } ] })" &&
expected_body==R"(
<h1>[I18N TESTING] Content not found, but at least the server is alive</h1>
<p>
[I18N TESTING] URL not found: /ROOT%23%3F/content/zimfile/invalid-article
</p>
<p>
[I18N TESTING] Make a full text search for <a href="/ROOT%23%3F/search?content=zimfile&pattern=invalid-article">invalid-article</a>
</p>
)" },
{ /* url */ "/ROOT%23%3F/raw/no-such-book/meta/Title",
expected_kiwix_response_data==R"({ "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "404-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "url-not-found", "params" : { "url" : "/ROOT%23%3F/raw/no-such-book/meta/Title" } } }, { "p" : { "msgid" : "no-such-book", "params" : { "BOOK_NAME" : "no-such-book" } } } ] })" &&
expected_body==R"(
@@ -914,130 +971,6 @@ TEST_F(ServerTest, Http404HtmlError)
}
}
std::string htmlEscape(std::string s)
{
s = replace(s, "&", "&amp;");
s = replace(s, "<", "&lt;");
s = replace(s, ">", "&gt;");
s = replace(s, "\"", "&quot;");
return s;
}
std::string escapeJsString(std::string s)
{
s = replace(s, "</script>", "</scr\\ipt>");
s = replace(s, "\"", "\\\"");
return s;
}
std::string expectedSexy404ErrorHtml(const std::string& url)
{
const auto urlWithoutQuery = replace(url, "\\?.*$", "");
const auto htmlSafeUrl = htmlEscape(urlWithoutQuery);
const auto jsSafeUrl = escapeJsString(urlWithoutQuery);
const std::string englishText[] = {
"Page not found",
"Not found!",
"Oops. Page not found.",
"The requested path was not found:",
"The content you&apos;re looking for may still be available, but it might be located at a different place within the ZIM file.",
"Please:",
"Try using the search function to find the content you want",
"Look for keywords or titles related to the information you&apos;re seeking",
"This approach should help you locate the desired content, even if the original link isn&apos;t working properly."
};
const std::string translatedText[] = {
"Page [I18N] not [TESTING] found",
"[I18N] Not found! [TESTING]",
"[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave.",
"[I18N TESTING] The requested path was not found (in fact, nothing was found instead, either):",
"Sh*t happens. [I18N TESTING] Take it easy!",
"[I18N TESTING] Try one of the following:",
"[I18N TESTING] Check the spelling of the URL path",
"[I18N TESTING] Press the dice button",
"Good luck! [I18N TESTING]"
};
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 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 = "&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n &lt;head&gt;\n &lt;meta charset=&quot;utf-8&quot;&gt;\n &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&quot; /&gt;\n &lt;title&gt;{{PAGE_TITLE}}&lt;/title&gt;\n &lt;link type=&quot;text/css&quot; href=&quot;{{root}}/skin/error.css?cacheid=b3fa90cf&quot; rel=&quot;Stylesheet&quot; /&gt;\n{{#KIWIX_RESPONSE_DATA}} &lt;script&gt;\n window.KIWIX_RESPONSE_TEMPLATE = &quot;{{KIWIX_RESPONSE_TEMPLATE}}&quot;;\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n &lt;/script&gt;{{/KIWIX_RESPONSE_DATA}}\n &lt;/head&gt;\n &lt;body&gt;\n &lt;header&gt;\n &lt;img src=&quot;{{root}}/skin/404.svg?cacheid=b6d648af&quot;\n alt=&quot;{{404_img_text}}&quot;\n aria-label=&quot;{{404_img_text}}&quot;\n title=&quot;{{404_img_text}}&quot;&gt;\n &lt;/header&gt;\n &lt;section class=&quot;intro&quot;&gt;\n &lt;h1&gt;{{PAGE_HEADING}}&lt;/h1&gt;\n &lt;p&gt;{{path_was_not_found_msg}}&lt;/p&gt;\n &lt;p&gt;&lt;code&gt;{{url_path}}&lt;/code&gt;&lt;/p&gt;\n &lt;/section&gt;\n &lt;section class=&quot;advice&quot;&gt;\n &lt;p&gt;{{advice.p1}}&lt;/p&gt;\n &lt;p class=&quot;list-intro&quot;&gt;{{advice.p2}}&lt;/p&gt;\n &lt;ul&gt;\n &lt;li&gt;{{advice.p3}}&lt;/li&gt;\n &lt;li&gt;{{advice.p4}}&lt;/li&gt;\n &lt;/ul&gt;\n &lt;p&gt;{{advice.p5}}&lt;/p&gt;\n &lt;/section&gt;\n &lt;/body&gt;\n&lt;/html&gt;\n";
window.KIWIX_RESPONSE_DATA = { "404_img_text" : { "msgid" : "404-img-text", "params" : { } }, "PAGE_HEADING" : { "msgid" : "new-404-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "new-404-page-title", "params" : { } }, "advice" : { "p1" : { "msgid" : "404-advice.p1", "params" : { } }, "p2" : { "msgid" : "404-advice.p2", "params" : { } }, "p3" : { "msgid" : "404-advice.p3", "params" : { } }, "p4" : { "msgid" : "404-advice.p4", "params" : { } }, "p5" : { "msgid" : "404-advice.p5", "params" : { } } }, "path_was_not_found_msg" : { "msgid" : "path-was-not-found", "params" : { } }, "root" : "/ROOT%23%3F", "url_path" : ")RAWSTRINGLITERAL"
+ // inject the URL
jsSafeUrl // inject the URL
+ // inject the URL
R"RAWSTRINGLITERAL(" };
</script>
</head>
<body>
<header>
<img src="/ROOT%23%3F/skin/404.svg?cacheid=b6d648af"
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" + t[4] + R"RAWSTRINGLITERAL(</p>
<p class="list-intro">)RAWSTRINGLITERAL" + t[5] + R"RAWSTRINGLITERAL(</p>
<ul>
<li>)RAWSTRINGLITERAL" + t[6] + R"RAWSTRINGLITERAL(</li>
<li>)RAWSTRINGLITERAL" + t[7] + R"RAWSTRINGLITERAL(</li>
</ul>
<p>)RAWSTRINGLITERAL" + t[8] + R"RAWSTRINGLITERAL(</p>
</section>
</body>
</html>
)RAWSTRINGLITERAL";
}
TEST_F(ServerTest, HttpSexy404HtmlError)
{
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
// XXX: inside a valid/existing book/ZIM-file. However it makes sense
// XXX: to preserve both cases.
"/ROOT%23%3F/content/invalid-book/whatever",
"/ROOT%23%3F/content/invalid-book/whatever?userlang=test",
"/ROOT%23%3F/content/zimfile/invalid-article",
"/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
// malicious URLs
R"(/ROOT%23%3F/content/"><svg onload=alert(1)>)",
R"(/ROOT%23%3F/content/zimfile/"><svg onload=alert(1)>)",
// XXX: This test case is against a "</script>" string appearing inside
// XXX: javascript code that will confuse the HTML parser
R"(/ROOT%23%3F/content/zimfile/</script>)",
};
for ( const auto& url : testUrls ) {
const TestContext ctx{ {"url", url} };
const auto r = zfs1_->GET(url.c_str());
EXPECT_EQ(r->status, 404) << ctx;
EXPECT_EQ(r->body, expectedSexy404ErrorHtml(url)) << ctx;
}
}
TEST_F(ServerTest, Http400HtmlError)
{
using namespace TestingOfHtmlResponses;
@@ -1213,98 +1146,35 @@ TEST_F(ServerTest, HttpXmlError)
}
}
std::string expectedSexy500ErrorHtml(const std::string& url,
const std::string& error)
TEST_F(ServerTest, 500)
{
const auto urlWithoutQuery = replace(url, "\\?.*$", "");
const auto htmlSafeUrl = htmlEscape(urlWithoutQuery);
const auto jsSafeUrl = escapeJsString(urlWithoutQuery);
const std::string englishText[] = {
"Internal Server Error",
"Page isn&apos;t working",
"Oops. Page isn&apos;t working.",
"The requested path cannot be properly delivered:",
};
const std::string translatedText[] = {
"[I18N] Internal Server Error [TESTING]",
"Page [I18N] isn&apos;t [TESTING] working",
"Oops. [I18N] Page isn&apos;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>
const std::string expectedBody = R"(<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<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" />
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
<title>Internal Server Error</title>
<script>
window.KIWIX_RESPONSE_TEMPLATE = "&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n &lt;head&gt;\n &lt;meta charset=&quot;utf-8&quot;&gt;\n &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no&quot; /&gt;\n &lt;title&gt;{{PAGE_TITLE}}&lt;/title&gt;\n &lt;link type=&quot;text/css&quot; href=&quot;{{root}}/skin/error.css?cacheid=b3fa90cf&quot; rel=&quot;Stylesheet&quot; /&gt;\n &lt;script&gt;\n window.KIWIX_RESPONSE_TEMPLATE = &quot;{{KIWIX_RESPONSE_TEMPLATE}}&quot;;\n window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};\n &lt;/script&gt;\n &lt;/head&gt;\n &lt;body&gt;\n &lt;header&gt;\n &lt;img src=&quot;{{root}}/skin/500.svg?cacheid=32eb0f20&quot;\n alt=&quot;{{500_img_text}}&quot;\n aria-label=&quot;{{500_img_text}}&quot;\n title=&quot;{{500_img_text}}&quot;&gt;\n &lt;/header&gt;\n &lt;section class=&quot;intro&quot;&gt;\n &lt;h1&gt;{{PAGE_HEADING}}&lt;/h1&gt;\n &lt;p&gt;{{PAGE_TEXT}}&lt;/p&gt;\n &lt;p&gt;&lt;code&gt;{{url_path}}&lt;/code&gt;&lt;/p&gt;\n &lt;/section&gt;\n{{#error}}\n &lt;section class=&quot;advice&quot;&gt;\n &lt;p&gt;{{error}}&lt;/p&gt;\n &lt;/section&gt;\n{{/error}}\n &lt;/body&gt;\n&lt;/html&gt;\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(" };
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." } } } ] };
</script>
</head>
<body>
<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>
<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>
</body>
</html>
)RAWSTRINGLITERAL";
}
)";
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;
{
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");
}
}
@@ -1316,12 +1186,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "ar",
"self_name": "الإنجليزية",
"translation_count": 44
"translation_count": 25
},
{
"iso_code": "bn",
"self_name": "বাংলা",
"translation_count": 34
"translation_count": 14
},
{
"iso_code": "br",
@@ -1341,7 +1211,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "de",
"self_name": "Deutsch",
"translation_count": 67
"translation_count": 57
},
{
"iso_code": "el",
@@ -1351,12 +1221,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "en",
"self_name": "English",
"translation_count": 93
"translation_count": 58
},
{
"iso_code": "es",
"self_name": "español",
"translation_count": 67
"translation_count": 49
},
{
"iso_code": "fi",
@@ -1366,7 +1236,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "fr",
"self_name": "Français",
"translation_count": 84
"translation_count": 57
},
{
"iso_code": "ha",
@@ -1376,32 +1246,22 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "he",
"self_name": "עברית",
"translation_count": 80
"translation_count": 57
},
{
"iso_code": "hi",
"self_name": "हिन्दी",
"translation_count": 59
},
{
"iso_code": "hu",
"self_name": "Magyar",
"translation_count": 32
"translation_count": 49
},
{
"iso_code": "hy",
"self_name": "Հայերեն",
"translation_count": 26
"translation_count": 15
},
{
"iso_code": "ia",
"self_name": "interlingua",
"translation_count": 84
},
{
"iso_code": "id",
"self_name": "Bahasa Inggris",
"translation_count": 68
"translation_count": 57
},
{
"iso_code": "ig",
@@ -1411,7 +1271,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "it",
"self_name": "italiano",
"translation_count": 58
"translation_count": 38
},
{
"iso_code": "ja",
@@ -1421,7 +1281,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "ko",
"self_name": "한국어",
"translation_count": 73
"translation_count": 15
},
{
"iso_code": "ku-latn",
@@ -1431,27 +1291,22 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "lb",
"self_name": "Lëtzebuergesch",
"translation_count": 48
"translation_count": 22
},
{
"iso_code": "mk",
"self_name": "македонски",
"translation_count": 81
"translation_count": 57
},
{
"iso_code": "ms",
"self_name": "Bahasa Melayu",
"translation_count": 14
},
{
"iso_code": "nb",
"self_name": "Engelsk",
"translation_count": 68
},
{
"iso_code": "nl",
"self_name": "Nederlands",
"translation_count": 68
"translation_count": 49
},
{
"iso_code": "nqo",
@@ -1466,27 +1321,17 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "pl",
"self_name": "Polski",
"translation_count": 32
"translation_count": 31
},
{
"iso_code": "pt-br",
"self_name": "Português",
"translation_count": 66
},
{
"iso_code": "pt",
"self_name": "português",
"translation_count": 67
},
{
"iso_code": "ro",
"self_name": "Engleză",
"translation_count": 68
"translation_count": 35
},
{
"iso_code": "ru",
"self_name": "русский",
"translation_count": 67
"translation_count": 57
},
{
"iso_code": "sc",
@@ -1501,7 +1346,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "skr-arab",
"self_name": "سرائیکی",
"translation_count": 33
"translation_count": 20
},
{
"iso_code": "sl",
@@ -1511,17 +1356,17 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "sq",
"self_name": "Shqip",
"translation_count": 67
"translation_count": 49
},
{
"iso_code": "sv",
"self_name": "Svenska",
"translation_count": 67
"translation_count": 57
},
{
"iso_code": "sw",
"self_name": "Kiswahili",
"translation_count": 58
"translation_count": 57
},
{
"iso_code": "te",
@@ -1536,12 +1381,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"iso_code": "zh-hans",
"self_name": "简体中文",
"translation_count": 68
"translation_count": 57
},
{
"iso_code": "zh-hant",
"self_name": "繁體中文",
"translation_count": 92
"translation_count": 57
}
])EXPECTEDRESPONSE");
}
@@ -1572,37 +1417,37 @@ TEST_F(ServerTest, UserLanguageControl)
"Default user language is English",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/* expected <h1> */ "Oops. Page not found."
/* expected <h1> */ "Not Found"
},
{
"userlang URL query parameter is respected",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/* expected <h1> */ "Oops. Page not found."
/* expected <h1> */ "Not Found"
},
{
"userlang URL query parameter is respected",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
/*Accept-Language:*/ "",
/* expected <h1> */ "[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave."
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"'Accept-Language: *' is handled",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "*",
/* expected <h1> */ "Oops. Page not found."
/* expected <h1> */ "Not Found"
},
{
"Accept-Language: header is respected",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/* expected <h1> */ "[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave."
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang query parameter takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "test",
/* expected <h1> */ "Oops. Page not found."
/* expected <h1> */ "Not Found"
},
{
"Most suitable language is selected from the Accept-Language header",
@@ -1610,7 +1455,7 @@ TEST_F(ServerTest, UserLanguageControl)
// with quality values) the most suitable language is selected.
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.9, en;q=0.2",
/* expected <h1> */ "[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave."
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"Most suitable language is selected from the Accept-Language header",
@@ -1618,7 +1463,7 @@ TEST_F(ServerTest, UserLanguageControl)
// with quality values) the most suitable language is selected.
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.2, en;q=0.9",
/* expected <h1> */ "Oops. Page not found."
/* expected <h1> */ "Not Found"
},
};

View File

@@ -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());
header = replace(header, "FIRSTRESULT", std::to_string(firstResultIndex+1));
const size_t lastResultIndex = std::min(totalResultCount, firstResultIndex + results.size() - 1);
header = replace(header, "FIRSTRESULT", std::to_string(firstResultIndex));
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 */ 0,
/* firstResultIndex */ 1,
/* results */ {},
/* pagination */ {}
},
@@ -951,7 +951,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 0,
/* totalResultCount */ 0,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {},
/* pagination */ {}
},
@@ -961,7 +961,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 0,
/* totalResultCount */ 1,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* 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 */ 0,
/* firstResultIndex */ 1,
/* 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 */ 0,
/* firstResultIndex */ 1,
/* 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 */ 0,
/* start */ 1,
/* resultsPerPage */ 0,
/* totalResultCount */ 2,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* 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 */ 0,
/* firstResultIndex */ 1,
/* results */ LARGE_SEARCH_RESULTS,
/* pagination */ {}
},
@@ -1068,7 +1068,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 100,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ LARGE_SEARCH_RESULTS,
/* pagination */ {}
},
@@ -1078,7 +1078,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
LARGE_SEARCH_RESULTS[0],
LARGE_SEARCH_RESULTS[1],
@@ -1099,10 +1099,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 5,
/* start */ 6,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 5,
/* firstResultIndex */ 6,
/* results */ {
LARGE_SEARCH_RESULTS[5],
LARGE_SEARCH_RESULTS[6],
@@ -1124,10 +1124,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 10,
/* start */ 11,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 10,
/* firstResultIndex */ 11,
/* results */ {
LARGE_SEARCH_RESULTS[10],
LARGE_SEARCH_RESULTS[11],
@@ -1150,10 +1150,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 15,
/* start */ 16,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 15,
/* firstResultIndex */ 16,
/* results */ {
LARGE_SEARCH_RESULTS[15],
LARGE_SEARCH_RESULTS[16],
@@ -1177,10 +1177,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 20,
/* start */ 21,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 20,
/* firstResultIndex */ 21,
/* results */ {
LARGE_SEARCH_RESULTS[20],
LARGE_SEARCH_RESULTS[21],
@@ -1204,10 +1204,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 25,
/* start */ 26,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 25,
/* firstResultIndex */ 26,
/* results */ {
LARGE_SEARCH_RESULTS[25],
LARGE_SEARCH_RESULTS[26],
@@ -1231,10 +1231,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 30,
/* start */ 31,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 30,
/* firstResultIndex */ 31,
/* results */ {
LARGE_SEARCH_RESULTS[30],
LARGE_SEARCH_RESULTS[31],
@@ -1257,10 +1257,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 35,
/* start */ 36,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 35,
/* firstResultIndex */ 36,
/* results */ {
LARGE_SEARCH_RESULTS[35],
LARGE_SEARCH_RESULTS[36],
@@ -1282,10 +1282,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 40,
/* start */ 41,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 40,
/* firstResultIndex */ 41,
/* results */ {
LARGE_SEARCH_RESULTS[40],
LARGE_SEARCH_RESULTS[41],
@@ -1305,10 +1305,10 @@ TEST(ServerSearchTest, searchResults)
{
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID,
/* start */ 21,
/* start */ 22,
/* resultsPerPage */ 3,
/* totalResultCount */ 44,
/* firstResultIndex */ 21,
/* firstResultIndex */ 22,
/* 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 */ 45,
/* start */ 46,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 45,
/* firstResultIndex */ 46,
/* results */ {},
/* pagination */ {
@@ -1357,7 +1357,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ 0,
/* resultsPerPage */ 10,
/* totalResultCount */ 2,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* 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 */ 0,
/* firstResultIndex */ 1,
/* 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 */ 0,
/* firstResultIndex */ 1,
/* results */ {
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
},
@@ -1401,7 +1401,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ 0,
/* resultsPerPage */ 10,
/* totalResultCount */ 1,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
},
@@ -1416,7 +1416,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ 0,
/* resultsPerPage */ 10,
/* totalResultCount */ 1,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
},
@@ -1431,7 +1431,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ 0,
/* resultsPerPage */ 10,
/* totalResultCount */ 1,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
},
@@ -1447,7 +1447,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 100,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ LARGE_SEARCH_RESULTS,
/* pagination */ {}
},
@@ -1458,7 +1458,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
LARGE_SEARCH_RESULTS[0],
LARGE_SEARCH_RESULTS[1],
@@ -1483,7 +1483,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
LARGE_SEARCH_RESULTS[0],
LARGE_SEARCH_RESULTS[1],
@@ -1509,7 +1509,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
LARGE_SEARCH_RESULTS[0],
LARGE_SEARCH_RESULTS[1],
@@ -1536,7 +1536,7 @@ TEST(ServerSearchTest, searchResults)
/* start */ -1,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 0,
/* firstResultIndex */ 1,
/* results */ {
LARGE_SEARCH_RESULTS[0],
LARGE_SEARCH_RESULTS[1],

View File

@@ -62,7 +62,6 @@ 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,
@@ -72,7 +71,6 @@ public: // types
struct Cfg
{
std::string root = "ROOT#?";
std::string contentServerUrl = "";
Options options = DEFAULT_OPTIONS;
Cfg(Options opts = DEFAULT_OPTIONS) : options(opts) {}
@@ -151,8 +149,6 @@ 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);
}

View File

@@ -1,217 +0,0 @@
/*
* 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));
}