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