mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-01-01 10:58:13 -05:00
Compare commits
68 Commits
feature/ip
...
smart_sugg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8fc60ed2f | ||
|
|
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 | ||
|
|
20abebd623 | ||
|
|
58a1af85b9 | ||
|
|
585f55d885 | ||
|
|
8b00c2eb22 | ||
|
|
c8bddd6cf4 | ||
|
|
5d1b6274a8 | ||
|
|
0de9bd0a99 | ||
|
|
b62274efdd |
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -33,17 +33,12 @@ jobs:
|
||||
HOME: /Users/runner
|
||||
steps:
|
||||
- name: Retrieve source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
brew update
|
||||
brew unlink python3
|
||||
# upgrade from python@3.12 to python@3.12.2 fails to overwrite those
|
||||
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.12 /usr/local/bin/idle3 /usr/local/bin/idle3.12 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.12 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.12 /usr/local/bin/python3.12-config
|
||||
brew install pkg-config ninja meson
|
||||
env:
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
brew install ninja meson
|
||||
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
@@ -105,7 +100,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
|
||||
|
||||
@@ -153,12 +148,12 @@ jobs:
|
||||
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"
|
||||
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:
|
||||
|
||||
6
.github/workflows/package.yml
vendored
6
.github/workflows/package.yml
vendored
@@ -32,9 +32,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 }}
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
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
|
||||
|
||||
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,
|
||||
|
||||
44
meson.build
44
meson.build
@@ -4,18 +4,46 @@ project('libkiwix', 'cpp',
|
||||
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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -515,28 +515,7 @@ bool InternalServer::start() {
|
||||
struct sockaddr* sockaddr = (m_ipMode==IpMode::ALL || m_ipMode==IpMode::IPV6)
|
||||
? (struct sockaddr*)&sockAddr6
|
||||
: (struct sockaddr*)&sockAddr4;
|
||||
#ifdef _WIN32
|
||||
SOCKET sock = INVALID_SOCKET;
|
||||
if (m_ipMode == IpMode::ALL || m_ipMode == IpMode::IPV6) {
|
||||
if ((sock = socket(AF_INET6, SOCK_STREAM, 0)) == INVALID_SOCKET) {
|
||||
std::cerr << "ERROR: Failed to create IPv6 socket" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
int opt = 0;
|
||||
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&opt, sizeof(opt)) != 0) {
|
||||
std::cerr << "ERROR: Failed to set IPV6_V6ONLY option" << std::endl;
|
||||
closesocket(sock);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (::bind(sock, (struct sockaddr*)&sockAddr6, sizeof(sockAddr6)) == SOCKET_ERROR) {
|
||||
std::cerr << "ERROR: Failed to bind IPv6 socket" << std::endl;
|
||||
closesocket(sock);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
mp_daemon = MHD_start_daemon(flags,
|
||||
m_port,
|
||||
NULL,
|
||||
@@ -544,21 +523,14 @@ bool InternalServer::start() {
|
||||
&staticHandlerCallback,
|
||||
this,
|
||||
MHD_OPTION_SOCK_ADDR, sockaddr,
|
||||
#ifdef _WIN32
|
||||
(sock == INVALID_SOCKET) ? MHD_OPTION_END : MHD_OPTION_LISTEN_SOCKET, sock,
|
||||
#endif
|
||||
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
|
||||
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
|
||||
MHD_OPTION_END);
|
||||
|
||||
if (mp_daemon == nullptr) {
|
||||
std::cerr << "ERROR: Unable to instantiate the HTTP daemon. The port " << m_port
|
||||
<< " may already be in use, or more permissions are required to open it. "
|
||||
std::cerr << "Unable to instantiate the HTTP daemon. The port " << m_port
|
||||
<< " is maybe already occupied or need more permissions to be open. "
|
||||
"Please try as root or with a port number higher or equal to 1024."
|
||||
<< std::endl;
|
||||
#ifdef _WIN32
|
||||
if (sock != INVALID_SOCKET) closesocket(sock);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
auto server_start_time = std::chrono::system_clock::now().time_since_epoch();
|
||||
@@ -803,6 +775,7 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
||||
}
|
||||
|
||||
const auto queryString = request.get_optional_param("term", std::string());
|
||||
const auto mode = request.get_optional_param("mode", std::string());
|
||||
const auto start = request.get_optional_param<unsigned int>("start", 0);
|
||||
unsigned int count = request.get_optional_param<unsigned int>("count", 10);
|
||||
if (count == 0) {
|
||||
@@ -821,13 +794,17 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
||||
);
|
||||
const auto lock(searcher->getLock());
|
||||
auto search = searcher->suggest(queryString);
|
||||
auto srs = search.getResults(start, count);
|
||||
|
||||
for(auto& suggestion: srs) {
|
||||
results.add(suggestion);
|
||||
if ( start == 0 && mode == "smart") {
|
||||
for(const auto& suggestion: search.getSmartSuggestions(count)) {
|
||||
results.add(suggestion);
|
||||
}
|
||||
} else {
|
||||
for(const auto& suggestion: search.getResults(start, count)) {
|
||||
results.add(suggestion);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Propose the fulltext search if possible */
|
||||
if (archive->hasFulltextIndex()) {
|
||||
results.addFTSearchSuggestion(request.get_user_language(), queryString);
|
||||
@@ -1113,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)
|
||||
@@ -1149,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
|
||||
@@ -1211,9 +1177,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());
|
||||
@@ -1258,9 +1222,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -190,6 +197,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
|
||||
|
||||
@@ -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
|
||||
@@ -229,7 +230,7 @@ IpAddress getBestPublicIps() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const char* const v4prefixes[] = { "192.168", "172.16", "10.0", "169.254" };
|
||||
const char* const v4prefixes[] = { "192.168", "172.16", "10.0" };
|
||||
for (const auto& prefix : v4prefixes) {
|
||||
for (const auto& kv : interfaces) {
|
||||
const auto& interfaceIps = kv.second;
|
||||
|
||||
@@ -384,9 +384,13 @@ void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
|
||||
: suggestion.getTitle();
|
||||
|
||||
result.set("label", escapeForJSON(label, DONT_ESCAPE_QUOTE));
|
||||
result.set("value", escapeForJSON(suggestion.getTitle(), DONT_ESCAPE_QUOTE));
|
||||
result.set("kind", "path");
|
||||
result.set("path", escapeForJSON(suggestion.getPath(), DONT_ESCAPE_QUOTE));
|
||||
if ( suggestion.getPath().empty() ) {
|
||||
result.set("kind", "modifiedquery");
|
||||
} else {
|
||||
result.set("kind", "path");
|
||||
result.set("value", escapeForJSON(suggestion.getTitle(), DONT_ESCAPE_QUOTE));
|
||||
result.set("path", escapeForJSON(suggestion.getPath(), DONT_ESCAPE_QUOTE));
|
||||
}
|
||||
result.set("first", m_data.is_empty_list());
|
||||
m_data.push_back(result);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -15,6 +15,7 @@ skin/i18n/he.json
|
||||
skin/i18n/hi.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 +24,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,8 @@
|
||||
skin/caret.png
|
||||
skin/bittorrent.png
|
||||
skin/magnet.png
|
||||
skin/404.svg
|
||||
skin/blocklink.svg
|
||||
skin/feed.svg
|
||||
skin/langSelector.svg
|
||||
skin/download.png
|
||||
@@ -11,9 +13,11 @@ skin/iso6391To3.js
|
||||
skin/isotope.pkgd.min.js
|
||||
skin/index.js
|
||||
skin/autoComplete/autoComplete.min.js
|
||||
skin/error.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 +46,7 @@ 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
|
||||
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 |
@@ -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,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Asma",
|
||||
"Hamoudak",
|
||||
"Ravan",
|
||||
"محمد أحمد عبد الفتاح"
|
||||
]
|
||||
@@ -12,7 +13,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 +22,31 @@
|
||||
"400-page-heading": "طلب غير صالح",
|
||||
"404-page-title": "المحتوى غير موجود",
|
||||
"404-page-heading": "لم يتم العثور عليه",
|
||||
"500-page-title": "خطأ في الخادم الداخلي",
|
||||
"500-page-heading": "خطأ في الخادم الداخلي",
|
||||
"fulltext-search-unavailable": "البحث عن النص الكامل غير متاح",
|
||||
"no-search-results": "محرك البحث عن النص الكامل غير متاح لهذا المحتوى.",
|
||||
"library-button-text": "اذهب لصفحة الترحيب",
|
||||
"500-page-title": "خطأ داخلي بالخادم",
|
||||
"500-page-heading": "خطأ داخلي بالخادم",
|
||||
"fulltext-search-unavailable": "البحث في كامل النص غير متاح",
|
||||
"no-search-results": "محرك البحث في كامل النص غير متوفر لهذا المحتوي.",
|
||||
"library-button-text": "توجه إلي صفحة الترحيب",
|
||||
"home-button-text": "انتقل إلى الصفحة الرئيسية لـ \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "اذهب إلى صفحة عشوائية",
|
||||
"searchbox-tooltip": "بحث \"{{BOOK_TITLE}}\"",
|
||||
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة."
|
||||
"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,6 +1,7 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"AnnaTheProgrammer777",
|
||||
"IMayBeABitShy",
|
||||
"Justman10000",
|
||||
"Lucas Werkmeister",
|
||||
@@ -56,12 +57,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>?",
|
||||
|
||||
@@ -20,9 +20,24 @@
|
||||
, "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 :/"
|
||||
, "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}}"
|
||||
@@ -78,4 +93,5 @@
|
||||
, "book-category.wikivoyage": "Wikivoyage"
|
||||
, "book-category.wiktionary": "Wiktionary"
|
||||
, "book-category.other": "Other"
|
||||
, "text-loading-content": "Loading Content"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"AlexanderFF",
|
||||
"Fitoschido",
|
||||
"Ovruni",
|
||||
"Sinopsistrans",
|
||||
"SpikeShroom",
|
||||
"Vis4valentine"
|
||||
]
|
||||
@@ -17,6 +18,7 @@
|
||||
"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}}",
|
||||
@@ -26,8 +28,14 @@
|
||||
"404-page-heading": "No encontrado",
|
||||
"500-page-title": "Error interno del servidor",
|
||||
"500-page-heading": "Error interno del servidor",
|
||||
"500-page-text": "Se produjo un error interno del servidor. Lo sentimos :/",
|
||||
"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}}'",
|
||||
"random-page-button-text": "Ir a una página seleccionada al azar",
|
||||
@@ -41,19 +49,30 @@
|
||||
"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.wikibooks": "Wikilibros",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -79,5 +79,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"
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
@@ -60,5 +60,15 @@
|
||||
"download-links-heading": "Discargar ligamines pro <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Discargar libro",
|
||||
"preview-book": "Previsualisation",
|
||||
"unknown-error": "Error incognite"
|
||||
"unknown-error": "Error incognite",
|
||||
"book-category.wikibooks": "Wikilibros",
|
||||
"book-category.wikinews": "Wikinovas",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversitate",
|
||||
"book-category.wikivoyage": "Wikiviage",
|
||||
"book-category.wiktionary": "Wiktionario",
|
||||
"book-category.other": "Altere"
|
||||
}
|
||||
|
||||
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,6 +3,7 @@
|
||||
"authors": [
|
||||
"Albano",
|
||||
"Beta16",
|
||||
"Clorofolle",
|
||||
"Luca.favorido",
|
||||
"McDutchie"
|
||||
]
|
||||
@@ -21,6 +22,9 @@
|
||||
"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 :/",
|
||||
@@ -33,15 +37,31 @@
|
||||
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
||||
"searchbox-tooltip": "Cerca '{{BOOK_TITLE}}'",
|
||||
"welcome-page-overzealous-filter": "Nessun risultato. Vuoi <a href=\"{{URL}}\">reimpostare il filtro</a>?",
|
||||
"search": "Cerca",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -7,15 +7,64 @@
|
||||
"name": "한국어",
|
||||
"suggest-full-text-search": "'{{{SEARCH_TERMS}}}' 포함...",
|
||||
"no-such-book": "해당 책이 없습니다: {{BOOK_NAME}}",
|
||||
"too-many-books": "요청된 책이 너무 많습니다. ({{NB_BOOKS}}) 한도는 {{LIMIT}}입니다.",
|
||||
"no-book-found": "선택 기준에 해당하는 책이 없습니다",
|
||||
"url-not-found": "\"{{url}}\" 요청 URL은 이 서버에서 찾을 수 없습니다.",
|
||||
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>에 대한 전문 검색을 수행해 보세요",
|
||||
"random-article-failure": "이런! 임의 문서를 선택하지 못했습니다 :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} 값은 원시 콘텐츠에 대한 유효한 요청이 아닙니다.",
|
||||
"invalid-request": "\"{{{url}}}\" 요청 URL은 유효한 요청이 아닙니다.",
|
||||
"no-value-for-arg": "{{ARGUMENT}} 인수에 지정된 값이 없습니다",
|
||||
"no-query": "지정된 쿼리가 없습니다.",
|
||||
"raw-entry-not-found": "{{DATATYPE}} 항목인 {{ENTRY}} 항목을 찾을 수 없습니다",
|
||||
"400-page-title": "잘못된 요청",
|
||||
"400-page-heading": "잘못된 요청",
|
||||
"404-page-title": "내용이 없습니다",
|
||||
"404-page-heading": "찾을 수 없음",
|
||||
"500-page-title": "내부 서버 오류",
|
||||
"500-page-heading": "내부 서버 오류",
|
||||
"500-page-text": "내부 서버 오류가 발생했습니다. 죄송합니다 :/",
|
||||
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
|
||||
"no-search-results": "이 콘텐츠에는 전문 검색 엔진을 사용할 수 없습니다.",
|
||||
"search-results-page-title": "검색: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b>에 대한 <b>{{COUNT}}</b>개 중 <b>{{START}}-{{END}}</b> 결과",
|
||||
"empty-search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b>의 결과가 없습니다",
|
||||
"search-result-book-info": "{{BOOK_TITLE}}에서",
|
||||
"word-count": "단어 {{COUNT}}개",
|
||||
"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,9 +15,13 @@
|
||||
"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-text": "Et ass en interne Serverfeeler opgetrueden. Mir entschëllegen eis dofir :/",
|
||||
"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>",
|
||||
|
||||
@@ -78,5 +78,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",
|
||||
@@ -29,6 +31,8 @@
|
||||
"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",
|
||||
@@ -52,12 +56,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",
|
||||
@@ -17,6 +28,7 @@
|
||||
"500-page-heading": "Erro interno do servidor",
|
||||
"500-page-text": "Aconteceu um erro interno do servidor. Nós pedimos desculpas sobre isso :/",
|
||||
"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 +39,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,24 @@
|
||||
"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",
|
||||
"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 +95,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 :(",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Jopparn",
|
||||
"Larsa",
|
||||
"Rofiatmustapha12",
|
||||
"Sabelöga",
|
||||
"WikiPhoenix"
|
||||
@@ -55,12 +56,22 @@
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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,21 @@
|
||||
, "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]"
|
||||
, "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 +80,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"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"GuoPC",
|
||||
"IceButBin",
|
||||
"Kichin",
|
||||
"Prmsh",
|
||||
"StarrySky",
|
||||
"Sunai",
|
||||
"XtexChooser",
|
||||
@@ -17,7 +18,7 @@
|
||||
"no-book-found": "没有符合搜索要求的图书",
|
||||
"url-not-found": "在此服务器上找不到请求的 URL:{{url}}",
|
||||
"suggest-search": "对<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>进行全文搜索",
|
||||
"random-article-failure": "抱歉! 随机条目失败了 (〒﹏〒) 【好生草的表情(】",
|
||||
"random-article-failure": "抱歉! 随机条目失败了 (〒﹏〒)",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} 对原请求无效。",
|
||||
"invalid-request": "请求的URL无效:{{{url}}}",
|
||||
"no-value-for-arg": "参数{{ARGUMENT}}无值",
|
||||
@@ -75,5 +76,6 @@
|
||||
"book-category.wikiversity": "维基学院",
|
||||
"book-category.wikivoyage": "维基导游",
|
||||
"book-category.wiktionary": "维基词典",
|
||||
"book-category.other": "其他"
|
||||
"book-category.other": "其他",
|
||||
"text-loading-content": "内容加载中"
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
"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}}」篩選",
|
||||
"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>的連結",
|
||||
@@ -79,5 +79,6 @@
|
||||
"book-category.wikiversity": "維基學院",
|
||||
"book-category.wikivoyage": "維基導遊",
|
||||
"book-category.wiktionary": "維基詞典",
|
||||
"book-category.other": "其他"
|
||||
"book-category.other": "其他",
|
||||
"text-loading-content": "正在載入內容"
|
||||
}
|
||||
|
||||
@@ -238,7 +238,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)
|
||||
|
||||
@@ -15,6 +15,35 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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 {
|
||||
background-color: #00b4e4;
|
||||
color: white;
|
||||
@@ -96,6 +125,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": 43
|
||||
},
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 14
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"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": 77
|
||||
},
|
||||
{
|
||||
"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": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
@@ -62,22 +62,27 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 57
|
||||
"translation_count": 69
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
"self_name": "हिन्दी",
|
||||
"translation_count": 49
|
||||
"translation_count": 59
|
||||
},
|
||||
{
|
||||
"iso_code": "hy",
|
||||
"self_name": "Հայերեն",
|
||||
"translation_count": 15
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "ia",
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 57
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "id",
|
||||
"self_name": "Bahasa Inggris",
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "ig",
|
||||
@@ -87,7 +92,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 38
|
||||
"translation_count": 54
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -97,7 +102,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ko",
|
||||
"self_name": "한국어",
|
||||
"translation_count": 15
|
||||
"translation_count": 64
|
||||
},
|
||||
{
|
||||
"iso_code": "ku-latn",
|
||||
@@ -107,22 +112,27 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "lb",
|
||||
"self_name": "Lëtzebuergesch",
|
||||
"translation_count": 22
|
||||
"translation_count": 43
|
||||
},
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 57
|
||||
"translation_count": 76
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
"self_name": "Bahasa Melayu",
|
||||
"translation_count": 14
|
||||
},
|
||||
{
|
||||
"iso_code": "nb",
|
||||
"self_name": "Engelsk",
|
||||
"translation_count": 50
|
||||
},
|
||||
{
|
||||
"iso_code": "nl",
|
||||
"self_name": "Nederlands",
|
||||
"translation_count": 49
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "nqo",
|
||||
@@ -137,17 +147,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": 65
|
||||
},
|
||||
{
|
||||
"iso_code": "pt",
|
||||
"self_name": "português",
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "ro",
|
||||
"self_name": "Engleză",
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
"self_name": "русский",
|
||||
"translation_count": 57
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "sc",
|
||||
@@ -162,7 +182,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "skr-arab",
|
||||
"self_name": "سرائیکی",
|
||||
"translation_count": 20
|
||||
"translation_count": 31
|
||||
},
|
||||
{
|
||||
"iso_code": "sl",
|
||||
@@ -172,17 +192,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 +217,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": 76
|
||||
}
|
||||
]
|
||||
@@ -92,14 +92,17 @@ 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()
|
||||
{
|
||||
const uriEncodedBookName = encodeURIComponent(currentBook);
|
||||
const userLang = viewerState.uiLanguage;
|
||||
return `${root}/suggest?userlang=${userLang}&content=${uriEncodedBookName}`;
|
||||
return `${root}/suggest?userlang=${userLang}&mode=smart&content=${uriEncodedBookName}`;
|
||||
}
|
||||
|
||||
function setTitle(element, text) {
|
||||
@@ -183,9 +186,13 @@ function closeSuggestions() {
|
||||
}
|
||||
}
|
||||
|
||||
function setSearchQuery(text) {
|
||||
document.getElementById("kiwixsearchbox").value = text;
|
||||
}
|
||||
|
||||
function updateSearchBoxForLocationChange() {
|
||||
closeSuggestions();
|
||||
document.getElementById("kiwixsearchbox").value = getSearchPattern();
|
||||
setSearchQuery(getSearchPattern());
|
||||
}
|
||||
|
||||
function updateSearchBoxForBookChange() {
|
||||
@@ -259,22 +266,7 @@ 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);
|
||||
}
|
||||
|
||||
function handle_content_url_change() {
|
||||
@@ -342,6 +334,17 @@ 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");
|
||||
@@ -349,13 +352,14 @@ function onClickEvent(e) {
|
||||
const target_href = getRealHref(target);
|
||||
const target_url = new URL(target_href, iframeDocument.location);
|
||||
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);
|
||||
@@ -409,6 +413,10 @@ function setup_external_link_blocker() {
|
||||
let viewerSetupComplete = false;
|
||||
|
||||
function on_content_load() {
|
||||
const loader = document.getElementById("kiwix__loader");
|
||||
|
||||
contentIframe.classList.remove("hidden");
|
||||
loader.style.display = "none";
|
||||
if ( viewerSetupComplete ) {
|
||||
handle_content_url_change();
|
||||
}
|
||||
@@ -460,7 +468,8 @@ function setupSuggestions() {
|
||||
resultItem: {
|
||||
element: (item, data) => {
|
||||
const uriEncodedBookName = encodeURIComponent(currentBook);
|
||||
let url;
|
||||
const linkText = htmlDecode(data.value.label);
|
||||
let url, modifiedQuery;
|
||||
if (data.value.kind == "path") {
|
||||
// The double quote and backslash symbols are included in the list
|
||||
// of special symbols to URI-encode so that the resulting URL can
|
||||
@@ -469,15 +478,18 @@ function setupSuggestions() {
|
||||
const path = htmlDecode(data.value.path);
|
||||
const quasiUriEncodedPath = quasiUriEncode(path, '#?"\\');
|
||||
url = `/content/${uriEncodedBookName}/${quasiUriEncodedPath}`;
|
||||
} else {
|
||||
} else if (data.value.kind == "pattern") {
|
||||
const pattern = encodeURIComponent(htmlDecode(data.value.value));
|
||||
url = `/search?content=${uriEncodedBookName}&pattern=${pattern}`;
|
||||
} else { // data.value.kind == "modifiedquery"
|
||||
modifiedQuery = htmlDecode(linkText);
|
||||
}
|
||||
// url can't contain any double quote and/or backslash symbols
|
||||
// since they should have been URI-encoded. Therefore putting it
|
||||
// inside double quotes should result in valid javascript.
|
||||
const jsAction = `gotoUrl("${url}")`;
|
||||
const linkText = htmlDecode(data.value.label);
|
||||
const jsAction = url
|
||||
? `gotoUrl("${url}")`
|
||||
: `setSearchQuery("${modifiedQuery}")`;
|
||||
item.innerHTML = makeJSLink(jsAction, linkText, 'class="suggest"');
|
||||
},
|
||||
highlight: "autoComplete_highlight",
|
||||
@@ -597,6 +609,12 @@ 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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
</div>
|
||||
</a>
|
||||
<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>
|
||||
|
||||
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>
|
||||
@@ -33,6 +33,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 +82,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%"
|
||||
|
||||
@@ -1189,7 +1189,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" <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=3948b846\"\n" \
|
||||
" rel=\"Stylesheet\"\n" \
|
||||
" />\n" \
|
||||
" <link\n" \
|
||||
@@ -1251,7 +1251,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >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 +1278,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >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 +1305,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >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" \
|
||||
|
||||
347
test/server.cpp
347
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=cc456f1f" },
|
||||
{ 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=3948b846" },
|
||||
{ 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" },
|
||||
{ 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=f8c5f4bf" },
|
||||
{ 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=a83f0e13" },
|
||||
|
||||
{ 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,14 @@ 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/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" },
|
||||
@@ -172,8 +180,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 +286,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=3948b846"
|
||||
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 +296,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=a83f0e13" 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=cc456f1f" 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 +327,13 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cac
|
||||
},
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/viewer",
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=3948b846" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?cacheid=80d56607" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=f2d376c4" 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=a83f0e13" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=f8c5f4bf" 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 +345,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 +839,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 +909,131 @@ 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)
|
||||
{
|
||||
using namespace TestingOfHtmlResponses;
|
||||
const std::vector<std::string> testUrls {
|
||||
// XXX: Nicer 404 error page no longer hints whether the error
|
||||
// XXX: is because of the missing book/ZIM-file or a missing article
|
||||
// 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;
|
||||
@@ -1186,12 +1249,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ar",
|
||||
"self_name": "الإنجليزية",
|
||||
"translation_count": 25
|
||||
"translation_count": 43
|
||||
},
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 14
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
@@ -1211,7 +1274,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "de",
|
||||
"self_name": "Deutsch",
|
||||
"translation_count": 57
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "el",
|
||||
@@ -1221,12 +1284,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "en",
|
||||
"self_name": "English",
|
||||
"translation_count": 58
|
||||
"translation_count": 77
|
||||
},
|
||||
{
|
||||
"iso_code": "es",
|
||||
"self_name": "español",
|
||||
"translation_count": 49
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "fi",
|
||||
@@ -1236,7 +1299,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 57
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
@@ -1246,22 +1309,27 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 57
|
||||
"translation_count": 69
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
"self_name": "हिन्दी",
|
||||
"translation_count": 49
|
||||
"translation_count": 59
|
||||
},
|
||||
{
|
||||
"iso_code": "hy",
|
||||
"self_name": "Հայերեն",
|
||||
"translation_count": 15
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "ia",
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 57
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "id",
|
||||
"self_name": "Bahasa Inggris",
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "ig",
|
||||
@@ -1271,7 +1339,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 38
|
||||
"translation_count": 54
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -1281,7 +1349,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ko",
|
||||
"self_name": "한국어",
|
||||
"translation_count": 15
|
||||
"translation_count": 64
|
||||
},
|
||||
{
|
||||
"iso_code": "ku-latn",
|
||||
@@ -1291,22 +1359,27 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "lb",
|
||||
"self_name": "Lëtzebuergesch",
|
||||
"translation_count": 22
|
||||
"translation_count": 43
|
||||
},
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 57
|
||||
"translation_count": 76
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
"self_name": "Bahasa Melayu",
|
||||
"translation_count": 14
|
||||
},
|
||||
{
|
||||
"iso_code": "nb",
|
||||
"self_name": "Engelsk",
|
||||
"translation_count": 50
|
||||
},
|
||||
{
|
||||
"iso_code": "nl",
|
||||
"self_name": "Nederlands",
|
||||
"translation_count": 49
|
||||
"translation_count": 68
|
||||
},
|
||||
{
|
||||
"iso_code": "nqo",
|
||||
@@ -1321,17 +1394,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": 65
|
||||
},
|
||||
{
|
||||
"iso_code": "pt",
|
||||
"self_name": "português",
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "ro",
|
||||
"self_name": "Engleză",
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
"self_name": "русский",
|
||||
"translation_count": 57
|
||||
"translation_count": 67
|
||||
},
|
||||
{
|
||||
"iso_code": "sc",
|
||||
@@ -1346,7 +1429,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "skr-arab",
|
||||
"self_name": "سرائیکی",
|
||||
"translation_count": 20
|
||||
"translation_count": 31
|
||||
},
|
||||
{
|
||||
"iso_code": "sl",
|
||||
@@ -1356,17 +1439,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 +1464,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": 76
|
||||
}
|
||||
])EXPECTEDRESPONSE");
|
||||
}
|
||||
@@ -1417,37 +1500,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 +1538,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 +1546,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."
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user