mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-24 06:57:59 -05:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba598bda9b | ||
|
|
0ad2710884 | ||
|
|
ab31ed9ca5 | ||
|
|
86cbc303cb | ||
|
|
19d9bc36c8 | ||
|
|
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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
@@ -81,6 +83,7 @@ function quasiUriEncode(s, specialSymbols) {
|
||||
|
||||
function performSearch() {
|
||||
const searchbox = document.getElementById('kiwixsearchbox');
|
||||
if (!searchbox.value.trim()) { return;}
|
||||
const q = encodeURIComponent(searchbox.value);
|
||||
gotoUrl(`/search?books.name=${currentBook}&pattern=${q}&userlang=${viewerState.uiLanguage}`);
|
||||
}
|
||||
@@ -92,7 +95,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 +265,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 +342,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 +416,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 +589,7 @@ function setupViewer() {
|
||||
|
||||
const kiwixToolBarWrapper = document.getElementById('kiwixtoolbarwrapper');
|
||||
if ( ! viewerSettings.toolbarEnabled ) {
|
||||
finishViewerSetup();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,15 +627,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=6192cae1" },
|
||||
{ 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=6192cae1" 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."
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -689,6 +689,24 @@ bool isSubSnippet(std::string subSnippet, const std::string& superSnippet)
|
||||
#define RAYCHARLESZIMID "6f1d19d0-633f-087b-fb55-7ac324ff9baf"
|
||||
#define EXAMPLEZIMID "5dc0b3af-5df2-0925-f0ca-d2bf75e78af6"
|
||||
|
||||
const std::vector<SearchResult> YELLOW_SEARCH_RESULTS = {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
/*title*/ "Eleanor Rigby",
|
||||
/*snippet*/ R"SNIPPET(...-side "<b>Yellow</b> Submarine" (double A-side) Released 5)SNIPPET" "\xC2\xA0" "August" "\xC2\xA0" "1966" "\xC2\xA0" R"SNIPPET((1966-08-05) Format 7-inch single Recorded 28–29 April & 6 June 1966 Studio EMI, London Genre Baroque pop, art rock Length 2:08 Label Parlophone (UK), Capitol (US) Songwriter(s) Lennon–McCartney Producer(s) George Martin The Beatles singles chronology "Paperback Writer" (1966) "Eleanor Rigby" / "<b>Yellow</b> Submarine" (1966) "Strawberry Fields Forever" / "Penny Lane" (1967) Music video "Eleanor Rigby" on YouTube The song continued the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "201"
|
||||
),
|
||||
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...standard and has been recorded by many artists, including Greta Keller, for whom some say McKuen wrote the lyrics. "If You Go Away" Single by Damita Jo from the album If You Go Away B-side "<b>Yellow</b> Days" Released 1966 Genre Jazz Length 3:49 Label Epic Records Songwriter(s) Jacques Brel, Rod McKuen Producer(s) Bob Morgan Damita Jo singles chronology "Gotta Travel On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
)
|
||||
};
|
||||
|
||||
struct TestData
|
||||
{
|
||||
struct PaginationEntry
|
||||
@@ -758,8 +776,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 +958,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 0,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {},
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -951,7 +969,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 0,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {},
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -961,7 +979,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,24 +997,8 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
/*title*/ "Eleanor Rigby",
|
||||
/*snippet*/ R"SNIPPET(...-side "<b>Yellow</b> Submarine" (double A-side) Released 5 August 1966 (1966-08-05) Format 7-inch single Recorded 28–29 April & 6 June 1966 Studio EMI, London Genre Baroque pop, art rock Length 2:08 Label Parlophone (UK), Capitol (US) Songwriter(s) Lennon–McCartney Producer(s) George Martin The Beatles singles chronology "Paperback Writer" (1966) "Eleanor Rigby" / "<b>Yellow</b> Submarine" (1966) "Strawberry Fields Forever" / "Penny Lane" (1967) Music video "Eleanor Rigby" on YouTube The song continued the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "201"
|
||||
),
|
||||
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...standard and has been recorded by many artists, including Greta Keller, for whom some say McKuen wrote the lyrics. "If You Go Away" Single by Damita Jo from the album If You Go Away B-side "<b>Yellow</b> Days" Released 1966 Genre Jazz Length 3:49 Label Epic Records Songwriter(s) Jacques Brel, Rod McKuen Producer(s) Bob Morgan Damita Jo singles chronology "Gotta Travel On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
)
|
||||
},
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ YELLOW_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
@@ -1005,50 +1007,18 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
/*title*/ "Eleanor Rigby",
|
||||
/*snippet*/ R"SNIPPET(...-side "<b>Yellow</b> Submarine" (double A-side) Released 5 August 1966 (1966-08-05) Format 7-inch single Recorded 28–29 April & 6 June 1966 Studio EMI, London Genre Baroque pop, art rock Length 2:08 Label Parlophone (UK), Capitol (US) Songwriter(s) Lennon–McCartney Producer(s) George Martin The Beatles singles chronology "Paperback Writer" (1966) "Eleanor Rigby" / "<b>Yellow</b> Submarine" (1966) "Strawberry Fields Forever" / "Penny Lane" (1967) Music video "Eleanor Rigby" on YouTube The song continued the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "201"
|
||||
),
|
||||
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...standard and has been recorded by many artists, including Greta Keller, for whom some say McKuen wrote the lyrics. "If You Go Away" Single by Damita Jo from the album If You Go Away B-side "<b>Yellow</b> Days" Released 1966 Genre Jazz Length 3:49 Label Epic Records Songwriter(s) Jacques Brel, Rod McKuen Producer(s) Bob Morgan Damita Jo singles chronology "Gotta Travel On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
)
|
||||
},
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ YELLOW_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
{
|
||||
/* query */ "pattern=yellow&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 1,
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/Eleanor_Rigby",
|
||||
/*title*/ "Eleanor Rigby",
|
||||
/*snippet*/ R"SNIPPET(...-side "<b>Yellow</b> Submarine" (double A-side) Released 5 August 1966 (1966-08-05) Format 7-inch single Recorded 28–29 April & 6 June 1966 Studio EMI, London Genre Baroque pop, art rock Length 2:08 Label Parlophone (UK), Capitol (US) Songwriter(s) Lennon–McCartney Producer(s) George Martin The Beatles singles chronology "Paperback Writer" (1966) "Eleanor Rigby" / "<b>Yellow</b> Submarine" (1966) "Strawberry Fields Forever" / "Penny Lane" (1967) Music video "Eleanor Rigby" on YouTube The song continued the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "201"
|
||||
),
|
||||
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...standard and has been recorded by many artists, including Greta Keller, for whom some say McKuen wrote the lyrics. "If You Go Away" Single by Damita Jo from the album If You Go Away B-side "<b>Yellow</b> Days" Released 1966 Genre Jazz Length 3:49 Label Epic Records Songwriter(s) Jacques Brel, Rod McKuen Producer(s) Bob Morgan Damita Jo singles chronology "Gotta Travel On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
)
|
||||
},
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ YELLOW_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
@@ -1057,18 +1027,28 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 100,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ LARGE_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
{
|
||||
/* query */ "pattern=yellow%20submarine&books.id=" RAYCHARLESZIMID,
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 0,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ { YELLOW_SEARCH_RESULTS[0] },
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
{
|
||||
/* query */ "pattern=jazz&books.id=" RAYCHARLESZIMID
|
||||
"&userlang=test",
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 100,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ LARGE_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -1078,7 +1058,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 +1079,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 +1104,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 +1130,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 +1157,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 +1184,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 +1211,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 +1237,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 +1262,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 +1285,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 +1314,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 +1337,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 +1351,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 +1366,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
|
||||
},
|
||||
@@ -1401,7 +1381,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
},
|
||||
@@ -1416,7 +1396,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
|
||||
},
|
||||
@@ -1431,7 +1411,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
},
|
||||
@@ -1447,7 +1427,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* start */ -1,
|
||||
/* resultsPerPage */ 100,
|
||||
/* totalResultCount */ 44,
|
||||
/* firstResultIndex */ 1,
|
||||
/* firstResultIndex */ 0,
|
||||
/* results */ LARGE_SEARCH_RESULTS,
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -1458,7 +1438,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 +1463,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 +1489,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 +1516,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