Compare commits

..

130 Commits

Author SHA1 Message Date
Kelson
24faf84163 Merge pull request #1023 from kiwix/suggestions_with_control_characters
Control characters are escaped in suggestions JSON
2023-11-17 15:12:13 +01:00
Veloman Yunkan
571c09e00a Control characters are escaped in suggestions JSON
According to the JSON spec, control characters from U+0000 through U+001F
must NOT appear in strings unescaped.
2023-11-17 14:55:01 +01:00
Kelson
a959800173 Merge pull request #1024 from kiwix/release-13.0.0
Release 13.0.0
2023-11-17 14:13:31 +01:00
Emmanuel Engelhart
b2196ee7a9 13.0.0 Changelog 2023-11-17 14:11:20 +01:00
Emmanuel Engelhart
aea51c21ff Bump-up version to 13.0.0 2023-11-17 13:52:03 +01:00
Kelson
95d627afa1 Merge pull request #1022 from kiwix/viewer_toolbar_tweaks
Viewer toolbar improvements
2023-11-15 21:41:55 +01:00
Veloman Yunkan
183bdcf2c0 Updated tests depending on kiwix-serve resources 2023-11-15 16:35:06 +04:00
Veloman Yunkan
e1cf16ddea Better behavior on narrow screens
On media (screens) narrower than 420 pixels, the toolbar buttons
are hidden. Before this change, when made visible they were laid out
in two rows. This change places them in a single row and provides
some vertical spacing from the search-box.
2023-11-15 16:08:41 +04:00
Veloman Yunkan
a74df86fcf Continuity in responsive layout of the toolbar
Without this change, in the media width range [416, 420) the searchbox is
narrow while the toolbars button space is empty which doesn't look nice.
2023-11-15 15:45:28 +04:00
Veloman Yunkan
605c7f71e0 Right-aligned UI language selector button 2023-11-15 15:31:22 +04:00
Veloman Yunkan
f58d4a93e1 Viewer toolbar controls are now of the same height 2023-11-15 13:00:12 +04:00
Kelson
00032adce2 Merge pull request #1017 from kiwix/macos_13
Switch to macos-13.
2023-11-11 19:33:53 +01:00
Matthieu Gautier
f5e6502e04 Switch to macos-13. 2023-11-09 17:53:33 +01:00
Kelson
37274f7882 Merge pull request #1016 from kiwix/fix_query_with_dot
Do not index book's name as a phrase.
2023-11-08 17:31:15 +01:00
Matthieu Gautier
07ff4eab43 Do not index book's name as a phrase.
Fix #1004
2023-11-08 10:29:31 +01:00
Kelson
e89f4e2ac7 Merge pull request #1008 from kiwix/autocomplete_no_min
Add a non minified version of autoComplete.js
2023-11-07 20:41:59 +01:00
Matthieu Gautier
bcbdce6a9a Add a small comment on autoComplete.css telling where it comes from. 2023-11-07 11:13:09 +01:00
Matthieu Gautier
0effcdb23f Add unminified autoComplete.js and LICENSE file.
- LICENSE is the copy of LICENSE file in TarekRaafat/autoComplete.js
- autoComplete.js is
  `https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.js`
2023-11-07 11:07:56 +01:00
Matthieu Gautier
5c8dd0e8d3 Move autoComplete.min.js and autoComplete.css in a subdirectory.
This way we can easily identify which files is part of other project.
2023-11-07 11:04:27 +01:00
Matthieu Gautier
d2c031e047 Merge pull request #1013 from kiwix/add-fon-language-support 2023-11-07 08:29:25 +01:00
Emmanuel Engelhart
733b027c2f Add support of Fon language (no supported in libicu) 2023-11-04 15:34:42 +01:00
Kelson
e8b8c18297 Merge pull request #1009 from kiwix/kiwix_frontend_style_cleanup
Kiwix frontend style cleanup
2023-11-04 15:20:45 +01:00
Veloman Yunkan
29c33a7ad6 More economic use of vertical space on the library page 2023-10-28 21:20:33 +04:00
Veloman Yunkan
fd504c1166 Matched viewer toolbar color to that of the library page
Attempts to use the same color for buttons yielded poor results: viewer
toolbar buttons don't look nice on the dark background used for the
filter controls on the library page, whereas the light background of the
viewer toolbar buttons doesn't play well with the filters on the library
page which seem to be designed around the contrast effect.
2023-10-28 21:20:33 +04:00
Veloman Yunkan
0c05af658d Deduplicated styling of UI language selector
There was a slight difference (between index.css and taskbar.css) in the
margin values of the UI language selector button, however the values
taken from taskbar.css don't seem to have any visible impact on the
welcome/library page (controlled by index.css).
2023-10-28 21:20:33 +04:00
Veloman Yunkan
0c0b1f5971 Moved to kiwix.css some CSS with global effect
Moved from index.css into kiwix.css some CSS with global effect thus
making it apply to the viewer too.

Extra font-size directives in taskbar.css are needed to undo the effect
of 'font-size: 62.5%' now applied to the 'html' element type.
2023-10-28 21:20:33 +04:00
Veloman Yunkan
a65681d6f4 Shared styling of modal dialogs goes into kiwix.css 2023-10-28 21:20:33 +04:00
Veloman Yunkan
af27141320 Enter kiwix.css
The new file kiwix.css is intended to host the intersection of index.css
and taskbar.css. In this commit only font definitions have been moved
into it.
2023-10-28 21:20:33 +04:00
Veloman Yunkan
d2bb3d198c Moved font definition from template to CSS 2023-10-28 21:20:33 +04:00
Matthieu Gautier
a5db4a1fd5 Merge pull request #1006 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2023-10-24 15:33:21 +02:00
Matthieu Gautier
59f0070ecc Add new translations to resource files. 2023-10-24 15:13:07 +02:00
translatewiki.net
bd818d33af Localisation updates from https://translatewiki.net. 2023-10-23 13:10:04 +02:00
Kelson
16fbf15938 Merge pull request #1007 from computerscienceiscool/patch-1 2023-10-20 10:21:51 +02:00
JJ
8383265ac4 Update README.md
Fixed a couple of spelling errors
2023-10-20 00:58:52 -07:00
Kelson
0eb9a06736 Merge pull request #1003 from kiwix/nodiscard_aarch64
Do not use `[[nodiscard]]` attribute on compiler not supporting it.
2023-10-16 15:19:53 +02:00
Matthieu Gautier
01aa190c38 Do not use [[nodiscard]] attribute on compiler not supporting it.
On aarch64, we use gcc version 6.3.0 which doesn't support the
`[[nodiscard]]` attribute
(see https://en.cppreference.com/w/cpp/compiler_support/17)

So don't set the attribute if the attribute is not present.
2023-10-16 14:41:30 +02:00
Kelson
da891699ac Merge pull request #1005 from kiwix/language_selector_font_fix
Fixed the fonts in the viewer UI language selector
2023-10-16 11:34:38 +02:00
Veloman Yunkan
f9be9f98ce Fixed the fonts in the viewer UI language selector 2023-10-15 16:37:28 +04:00
Veloman Yunkan
22b55d36c6 Merge pull request #990 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2023-10-15 15:04:00 +04:00
Veloman Yunkan
2d86927e17 Registered new translations in the resource list 2023-10-15 14:47:32 +04:00
translatewiki.net
86be66a2d8 Localisation updates from https://translatewiki.net. 2023-10-12 13:09:48 +02:00
Matthieu Gautier
4425cd2122 Merge pull request #1001 from kiwix/magnet 2023-10-09 18:16:39 +02:00
renaud gaudin
ab0d7b6e80 updated index.js cacheid 2023-10-09 16:08:24 +00:00
renaud gaudin
cfc91b0967 Fixed #938: added hack for mirrobrain magnet URIs
Missing parameters are added to the magnet link for it to work properly
given it is mainly based on webseeds.
Each mirror serving the file is added as a webseed.
Params that requires URIEncoding are now encoded.

Introduces `makeURLSearchString()` to turn SearchParams into a string but only
URIEncoding some specific params.
2023-10-08 16:52:02 +02:00
Kelson
2650cdd7da Merge pull request #983 from kiwix/multiple-ci-cd-fixes
Multiple CI/CD fixes
2023-10-08 16:47:29 +02:00
Emmanuel Engelhart
efdb596561 Add Debian package building in CI 2023-10-08 16:43:10 +02:00
Emmanuel Engelhart
177e1d5da6 Use latest base image_variant v38 2023-10-08 16:43:10 +02:00
Emmanuel Engelhart
b861dfc9dd Use pinned version of Ubuntu for workflow 2023-10-08 16:43:10 +02:00
Emmanuel Engelhart
3fdbb5a990 Push on 'release' PPA triggered by 'release' event 2023-10-08 16:43:10 +02:00
Matthieu Gautier
e49abc1df1 Merge pull request #991 from kiwix/no_raw_pointer 2023-10-05 17:47:44 +02:00
Matthieu Gautier
9166b67c47 Do not allow SearchRendered to work on a delete nameMapper/Library.
By moving the nameMapper/library arguments in `getHtml`/`getXml` we avoid
any potential "use after free" of name mapper or library as they are not
stored.
2023-10-05 16:37:22 +02:00
Matthieu Gautier
1dc9705597 Introduce LibraryPtr and ConstLibraryPtr.
As we enforce the use of Library through a shared_ptr, let's simplify
user life (and code) with new "type".
2023-10-05 16:37:22 +02:00
Matthieu Gautier
5292f06fff Move back Library::Impl in Library.
As we now always use Library through a shared_ptr, we can make `Library`
not copiable and not movable.
So we can move back the data in `Library`, we don't care about move
constructor.
2023-10-05 16:37:22 +02:00
Matthieu Gautier
f8e7c3d476 Move the Library mutex in Library::Impl.
The why of this mutex is in `Library` is a bit complex.
It has been introduced in c2927ce when there was only `Library` and no
`std::unique_ptr<Impl>`.
As introducing the mutex imply implementing the move constructor, we have
split all data in `LibraryBase` (and keep a default move constructor here)
and add the mutex in `Library` (and implement a simple move constructor).

Later, in 090c2fd, we have move the `LibraryBase` to `Library::Impl`
(which should have been `Library::Data`).

So at the end, `Library::Impl` is never moved. We can move the `mutex` in
it and still simply implement move constructor for `Library`.
2023-10-05 16:37:22 +02:00
Matthieu Gautier
ead1474ead Make SearchRendered taking a const pointer. 2023-10-05 16:37:22 +02:00
Matthieu Gautier
1316dec37c Make the Server keep a shared_ptr instead of a raw NameMapper pointer.
Same as for `Library`, we want to be sure that the `NameMapper`
actually exists when the server is using it.
2023-10-05 16:37:22 +02:00
Matthieu Gautier
a5557eeb25 Make the Server keep a shared_ptr instead of a raw Library pointer.
We want to be sure that `Library` actually exists when we use it.
While it is not a silver bullet (user can still create a shared_ptr on
a raw pointer), making the `Server` keep `shared_ptr` on the library
help us a lot here.
2023-10-05 16:36:18 +02:00
Matthieu Gautier
efcbf6ef1e Make the UpdatableNameMapper keep a shared_ptr.
Same as `Manager`, we want to be sure that `Library` actually exists
when we use it.
2023-09-25 16:31:55 +02:00
Matthieu Gautier
139b561253 Make the Manager keep a shared_ptr instead of a raw Library reference.
We want to be sure that `Library` actually exists when we modify it.
While it is not a silver bullet (user can still create a shared_ptr on
a raw pointer), making the `Manager` keep `shared_ptr` on the library
help us a lot here.
2023-09-25 16:30:56 +02:00
Matthieu Gautier
c203e07ee9 Make the library creatable only within a shared_ptr. 2023-09-25 16:28:25 +02:00
Matthieu Gautier
49e99e7c22 Remove dumpers from the public API.
All those dumper were not used by any of our other projects.
They are only used internally, either by `Library::writeToFile` or the
server.
2023-09-19 16:46:58 +02:00
Kelson
e13324fbba Merge pull request #996 from kiwix/cpp17
Move to c++17.
2023-09-14 17:21:18 +02:00
Matthieu Gautier
c38ab3e5d7 Move to c++17.
All our compilers should handle c++17. Let's move on.
2023-09-14 17:21:02 +02:00
Matthieu Gautier
bde737f63b Merge pull request #997 from kiwix/cookieless_user_language_control 2023-09-11 14:04:48 +02:00
Veloman Yunkan
cc6aa9b162 Fixed userlang control on the library page too
This fix contains a small hack - in order to detect the default language
from browser language preference during the first visit, the library
page has to load /viewer_settings.js which contains that information.
2023-09-09 19:39:16 +04:00
Veloman Yunkan
9063450b5a Fixed userlang control in the viewer
Now the viewer stores the userlang preference in window.localStorage.
2023-09-09 19:39:16 +04:00
Veloman Yunkan
f8c3a1fd2e Added default user language to viewer_settings.js
The default user language determined from the value of "Accept-Language"
header is communicated to the client via the /viewer_settings.js
endpoint.
2023-09-09 19:37:49 +04:00
Veloman Yunkan
b5b98e7a61 RIP userlang cookie
This commit drops the usage of the userlang cookie in the backend but
not in the frontend. UI language control should be broken at this point
and will be fixed in the next few commits.
2023-09-09 19:37:49 +04:00
Veloman Yunkan
e7e8275a31 Made the language selector button visible
After upgrading my OS to Ubuntu 22.04 the language selector button
didn't show up in the viewer taskbar. Investigation shows that the id
used in the CSS was applied to the wrong HTML element (the enclosing
<a> rather than <img>).
2023-09-09 19:37:49 +04:00
Kelson
c6456cac42 Merge pull request #993 from kiwix/no_kinetic_package
Remove Ubuntu Kinetic from CI/CD (deprecated)
2023-08-24 17:15:22 +07:00
Emmanuel Engelhart
f0c0400485 Remove Ubuntu Kinetic from CI/CD (deprecated) 2023-08-24 11:15:19 +02:00
Matthieu Gautier
ccbeb154a5 Merge pull request #992 from kiwix/fix_rtd 2023-08-24 10:55:57 +02:00
Matthieu Gautier
0e8a2952d5 Always set html_theme in doc configuration.
Lat version of read the doc do not set a html_theme for us.
So we have to always set it.

See readthedocs/readthedocs.org#10638
2023-08-24 10:46:46 +02:00
Kelson
fe5e6c451d Merge pull request #977 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2023-08-18 10:37:54 +08:00
translatewiki.net
3966e8544b Localisation updates from https://translatewiki.net. 2023-08-17 13:10:24 +02:00
Matthieu Gautier
09476ededb Merge pull request #974 from kiwix/multipleCategories 2023-07-26 14:57:29 +02:00
Nikhil Tanwar
d47c4fa72f Unit tests for OPDS filtering by category
Added tests for multiple category filtering for zims
Added new test: catalog_v2_entries_filtered_by_category for entry filtering by category.
2023-07-26 18:15:47 +05:30
Nikhil Tanwar
c938101c70 Allow multiple category support
Created a generic function multipleQuery which takes:
1. string (representing a comma separated list)
2. param (the value to query on using Xapian).

Category and language query will use this function.
2023-07-26 18:15:45 +05:30
Matthieu Gautier
9c91fc7369 Merge pull request #967 from kiwix/opdsFilters 2023-07-26 14:00:06 +02:00
Nikhil Tanwar
385931f229 Move getLanguageSelfName to tools.h
This is a general utility which other ports can get use of.
Added tests
2023-07-26 16:02:32 +05:30
Nikhil Tanwar
8726de494c Tests for readLanguagesFromFeed and readCategoriesFromFeed
Added tests on a sample OPDS language and categories stream
2023-07-26 16:02:32 +05:30
Nikhil Tanwar
94d6bef402 Introduce readCategoriesFromFeed()
Added a function to load categories stored in an OPDS stream
2023-07-26 16:02:32 +05:30
Nikhil Tanwar
a28c2973e9 Introduce readLanguagesFromFeed()
Added a new function to read languages stored in an OPDS feed
2023-07-26 16:02:32 +05:30
Nikhil Tanwar
7feb89c30e Add remaining include files to meson.build
These files were overlooked during a merge of another PR
2023-07-26 16:02:32 +05:30
Matthieu Gautier
903dcd46d6 Merge pull request #978 from kiwix/fix_missing_includes 2023-07-25 16:32:26 +02:00
Matthieu Gautier
1be5424711 Add missing include.
`uint64_t` type (used by `beautifyFileSize`) need to be declared.
2023-07-25 13:37:31 +02:00
Matthieu Gautier
de517330f6 Merge pull request #971 from kiwix/beautifyPublic
Make beautifyFileSize public
2023-07-21 19:10:26 +02:00
Nikhil Tanwar
5c3a997de4 make beautifyFileSize public
This general function will be useful in other kiwix apps
2023-07-21 22:19:45 +05:30
Matthieu Gautier
cb74c9c7c7 Merge pull request #972 from kiwix/version_12.1.0 2023-07-20 16:01:30 +02:00
Matthieu Gautier
312cecf5f2 New version 12.1.0 2023-07-20 15:54:55 +02:00
Matthieu Gautier
a4d207a03a Merge pull request #964 from kiwix/translatewiki 2023-07-17 13:16:40 +02:00
translatewiki.net
7e36dd5ddb Localisation updates from https://translatewiki.net. 2023-07-17 13:08:18 +02:00
Matthieu Gautier
8ca809f8d9 Merge pull request #970 from kiwix/fix_macos_deps_name 2023-07-13 12:12:53 +02:00
Matthieu Gautier
fd22e34d58 Fix the name of the deps archive on macos. 2023-07-13 12:04:41 +02:00
Matthieu Gautier
3be1ddd8a9 Merge pull request #966 from kiwix/gungbe 2023-07-11 18:50:51 +02:00
Veloman Yunkan
e9d9d85427 Added Gungbe language code 2023-07-10 23:28:04 +04:00
Kelson
9599a31d2f Merge pull request #963 from kiwix/quasiuriencoded_suggestion_links
Quasi-URI-encoded suggestion links
2023-07-10 14:36:46 +02:00
Veloman Yunkan
4d60b106a2 Quasi-URI-encoded suggestion links
Before this fix suggestion links were built out of fully URI-encoded
book name and article path components despite the fact that this measure
was taken against only a few dangerous symbols such as '#', '?', '"' and
'\'.  However, URI-encoding the slash symbols in the path has some
undesirable side-effects (see #958).

Henceforth only the problematic symbols are encoded in the article path
component. The book name is still fully URI-encoded since I don't see
any counter-arguments.
2023-07-01 17:52:11 +04:00
Kelson
60cce602a3 Merge pull request #957 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2023-07-01 15:12:42 +02:00
translatewiki.net
abb81e7798 Localisation updates from https://translatewiki.net. 2023-07-01 15:12:24 +02:00
Kelson
1808857173 Merge pull request #954 from kiwix/search_remove_accent
Add a new test, showing accents is not properly handle in search endpoint.
2023-07-01 15:11:54 +02:00
Matthieu Gautier
556b94daae Fix server_search test.
The pattern (given as a query string) must be url decoded.
2023-06-30 12:04:42 +02:00
Matthieu Gautier
5f4dad60b9 Add a new test, showing accents is not properly handle in search endpoint. 2023-06-30 12:04:42 +02:00
Matthieu Gautier
820ffa8134 Merge pull request #962 from kiwix/fix_macos_build 2023-06-30 12:04:11 +02:00
renaud gaudin
f6f7214c99 Unlink and remove some linked python3 files
meson and ninja both depends on python3 which received an update.
This python3 update fails to install when linking.

This temp fix removes those files. Hopefully a future update will remove the need
for this hack
2023-06-30 11:26:55 +02:00
Matthieu Gautier
1f5a160d3d Merge pull request #961 from kiwix/issue950 2023-06-28 15:21:23 +02:00
Veloman Yunkan
f41007989b Fool-proof checking of book illustration presence
Now (in a library.xml flow) a book is considered to contain an
illustration only if both "faviconMimeType" and "favicon" attributes
are set to non-empty values.
2023-06-24 20:10:41 +04:00
Veloman Yunkan
f25d287afa Enhanced the test data to demonstrate issue#950
Presence of the "faviconMimeType" attribute in a book entry in library.xml
file is enough for libkiwix to assume that the book contains an illustration
(even if the "favicon" attribute is missing).
2023-06-24 20:03:54 +04:00
Kelson
550fc2fcf9 Merge pull request #959 from kiwix/clickable_external_links4
Fixed external links in the viewer iframe (final version)
2023-06-21 17:32:02 +02:00
Veloman Yunkan
96fb65f560 Guaranteed activation of external link blocking
This is a quickfix for the problem observed with external link blocking
during certain history navigation actions (when the cached iframe content is
loaded/restored before the viewer setup is completed).

Since external link blocking doesn't depend on the translations (that
are asynchronously loaded during the viewer setup) it can be performed
unconditionally. However, the current dependence of `on_content_load()`
on viewer setup has to be addressed too.
2023-06-18 19:49:47 +04:00
Veloman Yunkan
93197f8175 Mostly fixed external links in the viewer iframe
Before this fix clicking an external link in the viewer iframe had no
effect (other than an error being reported in the browser dev tools
console) because the attempt to navigate the top browser context was
suppressed due to sandboxing - the click handling code changed the
target of the link but navigating to that target was blocked. Now the
click handler works as follows:

1. Changes the target of the link to the catch page only if the
   link is going to be opened in a new tab or window (in this case
   sandboxing restrictions do not apply).

2. Otherwise directly navigates the viewer window to external URL
   or the catch page.

An unhandled scenario is opening an external link in a new tab/window
via a middle click or context menu - such events cannot be intercepted
and therefore there is no way of blocking external links accessed in
the said way.
2023-06-18 19:41:10 +04:00
Kelson
144945cfe0 Merge pull request #940 from kiwix/fix_for_issue912
PDF-friendly book home button in the viewer
2023-06-08 15:48:11 +02:00
Veloman Yunkan
c1ad65d515 PDF-friendly book home button in the viewer
In firefox, when PDF content is displayed in the viewer, changing the
viewer URL in the address bar had no effect. The most prominent
manifestation of this bug was the broken book home button but the same
issue was present even if the fragment component of the viewer URL was
edited manually. The bug was a result of

1. an optimization preventing any actions if the new content URL is the
   same as the old content URL (this was needed to break the infinite loop
   of mutual updates of the top-window and content window/iframe URLs when
   any one of them changes).

2. sandboxing of the iframe and inability to access the content URL in
   iframe because of cross-origin restrictions when the content is a PDF
   displayed by the builtin viewer.

Now that issue is fixed. A slight remaining defect is that the
addressbar URL is still not updated when a PDF file is loaded/displayed
in the viewer.
2023-06-08 17:32:50 +04:00
Matthieu Gautier
af2dfdccbc Merge pull request #956 from kiwix/no_bionic_package 2023-06-07 11:45:42 +02:00
Matthieu Gautier
f2072d87a0 [CI] Remove creation of package for bionic. 2023-06-07 11:26:55 +02:00
Matthieu Gautier
e9c3a7ff45 Merge pull request #955 from kiwix/fix_doc 2023-06-07 11:12:46 +02:00
Matthieu Gautier
a715203d3e Add readthedoc configuration file. 2023-06-07 10:54:51 +02:00
Kelson
552717b9ce Merge pull request #951 from OlCe2/oc-xapian_libzim_fix 2023-05-27 15:23:42 +03:00
Olivier Certner
0ed805ae6b meson.build: Fix detection of libzim built with Xapian
The dependency on libzim must be specified in compiler.has_header_symbol() for
it to find the header in all cases.

Fixes a configure error on FreeBSD:
"""
Header "zim/zim.h" has symbol "LIBZIM_WITH_XAPIAN" : NO

meson.build:33:2: ERROR: Problem encountered: Libzim seems to be compiled without xapian. Xapian support is mandatory.
"""
2023-05-27 12:53:57 +03:00
Kelson
b45cfd767a Merge pull request #949 from bentley/openbsd
Include netinet/in.h everywhere except Windows
2023-05-27 12:53:21 +03:00
Anthony J. Bentley
df164aefe5 Include netinet/in.h everywhere except Windows
According to POSIX, sockaddr_in is declared in netinet/in.h.
Some POSIX systems (notably OpenBSD and FreeBSD) declare it in
only this header, so including it is required. Others, like Linux,
are are more lax in exposing symbols to the namespace, providing
sockaddr_in via additional headers, but it does no harm to include
the standard header on such systems.
2023-05-27 12:42:29 +03:00
Kelson
58890a3f97 Merge pull request #952 from kiwix/use-focal-ci
Few CI changes (mostly Linux Bionic to Focal)
2023-05-27 12:42:12 +03:00
Emmanuel Engelhart
2d58142c58 No need anymore to change directory 2023-05-26 14:09:02 +02:00
Emmanuel Engelhart
0afa5e569c Ubuntu 20.04 & macos 11 as OS in CI 2023-05-26 14:07:03 +02:00
Emmanuel Engelhart
ae605dc26d Use latest version 27 of docker base image 2023-05-26 14:05:31 +02:00
Emmanuel Engelhart
d8f02ac225 Uses actions/checkout 2023-05-26 14:00:46 +02:00
Emmanuel Engelhart
e4595f357d Use Ubuntu Focal as CI base image 2023-05-26 13:54:52 +02:00
Veloman Yunkan
881c121142 Merge pull request #918 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2023-05-20 16:36:20 +04:00
Veloman Yunkan
2d51e1f0c6 Updated the list of translations 2023-05-20 16:18:12 +04:00
translatewiki.net
b24f681c24 Localisation updates from https://translatewiki.net. 2023-05-18 13:06:38 +02:00
96 changed files with 2844 additions and 878 deletions

View File

@@ -8,7 +8,7 @@ on:
jobs:
macOS:
runs-on: macos-12
runs-on: macos-13
env:
HOME: /Users/runner
steps:
@@ -18,11 +18,14 @@ jobs:
- name: Install packages
run: |
brew update
brew unlink python3
# upgrade from python@3.11.2_1 to python@3.11.3 fails to overwrite those
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.11 /usr/local/bin/idle3 /usr/local/bin/idle3.11 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.11 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.11 /usr/local/bin/python3.11-config
brew install pkg-config ninja meson
- name: Install dependencies
env:
ARCHIVE_NAME: deps2_osx_native_dyn_libkiwix.tar.xz
ARCHIVE_NAME: deps2_macos_native_dyn_libkiwix.tar.xz
run: |
wget -O- https://tmp.kiwix.org/ci/${{env.ARCHIVE_NAME}} | tar -xJ -C ${{env.HOME}}
@@ -54,19 +57,19 @@ jobs:
include:
- name: native_static
target: native_static
image_variant: bionic
image_variant: focal
lib_postfix: '/x86_64-linux-gnu'
- name: native_dyn
target: native_dyn
image_variant: bionic
image_variant: focal
lib_postfix: '/x86_64-linux-gnu'
- name: android_arm
target: android_arm
image_variant: bionic
image_variant: focal
lib_postfix: '/arm-linux-androideabi'
- name: android_arm64
target: android_arm64
image_variant: bionic
image_variant: focal
lib_postfix: '/aarch64-linux-android'
- name: win32_static
target: win32_static
@@ -78,22 +81,12 @@ jobs:
lib_postfix: '64'
env:
HOME: /home/runner
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
container:
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:36"
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:38"
steps:
- name: Checkout code
shell: python
run: |
from subprocess import check_call
from os import environ
command = [
'git', 'clone',
'https://github.com/${{github.repository}}',
'--depth=1',
'--branch', '${{ github.head_ref || github.ref_name }}'
]
check_call(command, cwd=environ['HOME'])
uses: actions/checkout@v3
- name: Install deps
shell: bash
run: |
@@ -116,7 +109,6 @@ jobs:
if [[ "${{matrix.target}}" =~ android_.* ]]; then
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
fi
cd $HOME/libkiwix
meson . build ${MESON_OPTION}
cd build
ninja
@@ -127,7 +119,7 @@ jobs:
if: startsWith(matrix.target, 'native_')
shell: bash
run: |
cd $HOME/libkiwix/build
cd build
meson test --verbose
ninja coverage
env:
@@ -138,4 +130,4 @@ jobs:
if: startsWith(matrix.target, 'native_')
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -5,18 +5,19 @@ on:
push:
branches:
- main
release:
types: [published]
jobs:
build-deb:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
distro:
- ubuntu-kinetic
- debian-unstable
- ubuntu-jammy
- ubuntu-focal
- ubuntu-bionic
steps:
- uses: actions/checkout@v3
@@ -39,13 +40,12 @@ jobs:
email: release+launchpad@kiwix.org
distro: ${{ matrix.distro }}
- uses: legoktm/gh-action-build-deb@ubuntu-kinetic
if: matrix.distro == 'ubuntu-kinetic'
name: Build package for ubuntu-kinetic
id: build-ubuntu-kinetic
- uses: legoktm/gh-action-build-deb@debian-unstable
if: matrix.distro == 'debian-unstable'
name: Build package for debian-unstable
id: build-debian-unstable
with:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
if: matrix.distro == 'ubuntu-jammy'
@@ -63,14 +63,6 @@ jobs:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: legoktm/gh-action-build-deb@ubuntu-bionic
if: matrix.distro == 'ubuntu-bionic'
name: Build package for ubuntu-bionic
id: build-ubuntu-bionic
with:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: actions/upload-artifact@v3
with:
name: Packages for ${{ matrix.distro }}
@@ -78,7 +70,7 @@ jobs:
- uses: legoktm/gh-action-dput@master
name: Upload dev package
# Only upload on pushes to git default branch
# Only upload on pushes to main
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
with:
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
@@ -87,10 +79,8 @@ jobs:
- uses: legoktm/gh-action-dput@master
name: Upload release package
# Only upload on pushes to master or tag
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && startswith(matrix.distro, 'ubuntu-')
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
with:
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
repository: ppa:kiwixteam/release
packages: output/*_source.changes

21
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,21 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

View File

@@ -1,3 +1,60 @@
libkiwix 13.0.0
===============
* Server:
- Improved look & feel of kiwix-serve UI (@veloman-yunkan #917 #1021)
- Increase tolerance to malformed (control characters) ZIM entry titles (@veloman-yunkan #1023)
- API allowing to filter many categories at once (@juuz0 #974)
- Cookie-less user language control (@veloman-yumkan #997)
- Hack to fix Mirrorbrain based broken magnet URLs (@rgaudin #1001)
* Fix handling of books with 'Name' metadata with dots (@mgautier #1016)
* New method beautifyFileSize() to provide nice-looking book sizes (@vuuz0 #971)
* Fix a few missing includes (@mgautierfr #978)
* New functions to read - kiwix-serve - languages and categories streams (@juuz0 #967)
* Add support of Fon language (@kelson42 #1013)
* C++17 code base compliancy (@mgautierfr #996)
* Use everywhere std::shared_ptr in place of raw pointer (@mgautierfr #991)
* Do not use [[nodiscard]] attribute on compiler not supporting it (@mgautierfr #1003)
* Add a non minified version of autoComplete.js (@mgautierfr #1008)
* Multiple CI/CD improvements (@kelson42 #982)
libkiwix 12.1.0
===============
* Server:
- Introduce a `/nojs` endpoint to browse catalog and zim files with a browser without js (@juuz0 #897)
- Translate the viewer (@veloman-yunkan #871 #846)
- Display `mul` on tile when zim is multi-languages (@juuz0 #934)
- Suggestion links point to the `/content` endpoint (@veloman-yunkan #862)
- Correctly compress web fonts in http answers (@kelson42 #856)
- Correctly encode link in suggestions (@veloman-yunkan #859 #860 #963)
- Correctly encode url redirection (@veloman-yunkan #866 #890)
- Properly handle user language, through cookies and http headers (@veloman-yunkan #849 #869)
- Fix url encoding (@veloman-yunkan #870)
- Fix viewer for viewer for SeaMonkey (@veloman-yunkan #887)
- Make the downloader threadsafe (@mgautierfr #886)
- Add RSS feed in the main page (pointing to the catalog) (@juuz0 #882 #920)
- Correctly set the mimetype for json and ico (@veloman-yunkan #892)
- `count=-1` correspond to unlimited count (instead of 0) (@veloman-yunkan #894)
- Keep the navigation bar on top (@juuz0 #896)
- Make the viewer's iframe "safe" (@veloman-yunkan #906 #930)
- Correctly escape search link in XML Opds output (@veloman-yunkan #936)
- Store values needed for the viewer js in the url fragment instead of the query string (@juuz0 #907)
- Get rid of legacy OPDS API usage in the viewer (@veloman-yunkan #939)
- Fix charset encoding declaration in OPDS response MIME types (@veloman-yunkan #942)
- Fix PDF in the viewer (@veloman-yunkan #940)
- Fix external links handling in the viewer (@veloman-yunkan #959)
- Add tests of searching with accents (@mgautierfs #954)
* Fix handling of missing illustration in the book (@veloman-yunkan #961)
* Add support for multi languages zim files (@veloman-yunkan #904)
* Fix includes for openbsd (@bentley #949)
* Fix pathes in git to allow git clone on Windows (@adamlamar #868)
* Switch to `main` as principal branch (instead of `master`) (@kelson42)
* Remove libkiwix android publisher from the repository (@kelson42 #884)
* Various fixes of meson and CI. (@mgautierfr @kelson42)
libkiwix 12.0.0
===============

View File

@@ -24,9 +24,9 @@ with the Libkiwix compilation itself, we recommend to have a look to
Preamble
--------
Although the Libkiwix can be (cross-)compiled on/for many sytems, the
Although the Libkiwix can be (cross-)compiled on/for many systems, the
following documentation explains how to do it on POSIX ones. It is
primarly thought for GNU/Linux systems and has been tested on recent
primarily thought for GNU/Linux systems and has been tested on recent
releases of Ubuntu and Fedora.
Dependencies
@@ -54,7 +54,7 @@ The following dependency needs to be available at runtime:
These dependencies may or may not be packaged by your operating
system. They may also be packaged but only in an older version. The
compilation script will tell you if one of them is missing or too old.
In the worse case, you will have to download and compile bleeding edge
In the worst case, you will have to download and compile bleeding edge
version by hand.
If you want to install these dependencies locally, then use the
@@ -201,7 +201,7 @@ To use JS provided by kiwix-serve you can use the following template to start wi
If you compile manually Libmicrohttpd, you might need to compile it
without GNU TLS, a bug here will empeach further compilation
without GNU TLS, a bug here will impeach further compilation
otherwise.
If the compilation still fails, you might need to get a more recent

View File

@@ -24,8 +24,6 @@ author = 'libkiwix-team'
# -- General configuration ---------------------------------------------------
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
@@ -42,9 +40,7 @@ templates_path = ['_templates']
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
if not on_rtd:
html_theme = 'sphinx_rtd_theme'
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,

View File

@@ -1,2 +1,3 @@
breathe
exhale
sphinx_rtd_theme

View File

@@ -34,6 +34,10 @@
#define KIWIX_LIBRARY_VERSION "20110515"
namespace Xapian {
class WritableDatabase;
};
namespace kiwix
{
@@ -105,6 +109,12 @@ class Filter {
Filter& acceptTags(const Tags& tags);
Filter& rejectTags(const Tags& tags);
/**
* Set the filter to only accept books in the specified category.
*
* Multiple categories can be specified as a comma-separated list (in
* which case a book in any of those categories will match).
*/
Filter& category(std::string category);
/**
@@ -167,31 +177,53 @@ class ZimSearcher : public zim::Searcher
std::mutex m_mutex;
};
template<typename, typename>
class ConcurrentCache;
template<typename, typename>
class MultiKeyCache;
using LibraryPtr = std::shared_ptr<Library>;
using ConstLibraryPtr = std::shared_ptr<const Library>;
// Some compiler we use don't have [[nodiscard]] attribute.
// We don't want to declare `create` with it in this case.
#define LIBKIWIX_NODISCARD
#if defined __has_cpp_attribute
#if __has_cpp_attribute (nodiscard)
#undef LIBKIWIX_NODISCARD
#define LIBKIWIX_NODISCARD [[nodiscard]]
#endif
#endif
/**
* A Library store several books.
*/
class Library
class Library: public std::enable_shared_from_this<Library>
{
// all data fields must be added in LibraryBase
mutable std::mutex m_mutex;
public:
typedef uint64_t Revision;
typedef std::vector<std::string> BookIdCollection;
typedef std::map<std::string, int> AttributeCounts;
typedef std::set<std::string> BookIdSet;
public:
private:
Library();
public:
LIBKIWIX_NODISCARD static LibraryPtr create() {
return LibraryPtr(new Library());
}
~Library();
/**
* Library is not a copiable object. However it can be moved.
*/
Library(const Library& ) = delete;
Library(Library&& );
Library(Library&& ) = delete;
void operator=(const Library& ) = delete;
Library& operator=(Library&& );
Library& operator=(Library&& ) = delete;
/**
* Add a book to the library.
@@ -362,19 +394,36 @@ class Library
private: // types
typedef const std::string& (Book::*BookStrPropMemFn)() const;
struct Impl;
struct Entry : Book
{
Library::Revision lastUpdatedRevision = 0;
};
private: // functions
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
BookIdCollection filterViaBookDB(const Filter& filter) const;
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
void updateBookDB(const Book& book);
void dropCache(const std::string& bookId);
private: //data
std::unique_ptr<Impl> mp_impl;
mutable std::mutex m_mutex;
Library::Revision m_revision;
std::map<std::string, Entry> m_books;
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
std::unique_ptr<ArchiveCache> mp_archiveCache;
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
std::unique_ptr<SearcherCache> mp_searcherCache;
std::vector<kiwix::Bookmark> m_bookmarks;
std::unique_ptr<Xapian::WritableDatabase> m_bookDB;
};
// We don't need it anymore and we don't want to polute any other potential usage
// of `LIBKIWIX_NODISCARD` token.
#undef LIBKIWIX_NODISCARD
}
#endif

View File

@@ -37,10 +37,10 @@ namespace kiwix
class LibraryManipulator
{
public: // functions
explicit LibraryManipulator(Library* library);
explicit LibraryManipulator(LibraryPtr library);
virtual ~LibraryManipulator();
Library& getLibrary() const { return library; }
LibraryPtr getLibrary() const { return library; }
bool addBookToLibrary(const Book& book);
void addBookmarkToLibrary(const Bookmark& bookmark);
@@ -52,7 +52,7 @@ class LibraryManipulator
virtual void booksWereRemovedFromLibrary();
private: // data
kiwix::Library& library;
LibraryPtr library;
};
/**
@@ -64,8 +64,8 @@ class Manager
typedef std::vector<std::string> Paths;
public: // functions
explicit Manager(LibraryManipulator* manipulator);
explicit Manager(Library* library);
explicit Manager(LibraryManipulator manipulator);
explicit Manager(LibraryPtr library);
/**
* Read a `library.xml` and add book in the file to the library.
@@ -163,7 +163,7 @@ class Manager
uint64_t m_itemsPerPage = 0;
protected:
std::shared_ptr<kiwix::LibraryManipulator> manipulator;
kiwix::LibraryManipulator manipulator;
bool readBookFromPath(const std::string& path, Book* book);
bool parseXmlDom(const pugi::xml_document& doc,

View File

@@ -4,8 +4,6 @@ headers = [
'common.h',
'library.h',
'manager.h',
'libxml_dumper.h',
'opds_dumper.h',
'downloader.h',
'search_renderer.h',
'server.h',

View File

@@ -59,7 +59,7 @@ class HumanReadableNameMapper : public NameMapper {
class UpdatableNameMapper : public NameMapper {
typedef std::shared_ptr<NameMapper> NameMapperHandle;
public:
UpdatableNameMapper(Library& library, bool withAlias);
UpdatableNameMapper(std::shared_ptr<Library> library, bool withAlias);
virtual std::string getNameForId(const std::string& id) const;
virtual std::string getIdForName(const std::string& name) const;
@@ -71,7 +71,7 @@ class UpdatableNameMapper : public NameMapper {
private:
mutable std::mutex mutex;
Library& library;
std::shared_ptr<Library> library;
NameMapperHandle nameMapper;
const bool withAlias;
};

View File

@@ -37,29 +37,11 @@ class SearchRenderer
/**
* Construct a SearchRenderer from a SearchResultSet.
*
* The constructed version of the SearchRenderer will not introduce
* the book name for each result. It is better to use the other constructor
* with a Library pointer to have a better html page.
*
* @param srs The `SearchResultSet` to render.
* @param mapper The `NameMapper` to use to do the rendering.
* @param start The start offset used for the srs.
* @param estimatedResultCount The estimatedResultCount of the whole search
*/
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
unsigned int start, unsigned int estimatedResultCount);
/**
* Construct a SearchRenderer from a SearchResultSet.
*
* @param srs The `SearchResultSet` to render.
* @param mapper The `NameMapper` to use to do the rendering.
* @param library The `Library` to use to look up book details for search results.
* @param start The start offset used for the srs.
* @param estimatedResultCount The estimatedResultCount of the whole search
*/
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
unsigned int start, unsigned int estimatedResultCount);
SearchRenderer(zim::SearchResultSet srs, unsigned int start, unsigned int estimatedResultCount);
~SearchRenderer();
@@ -90,24 +72,32 @@ class SearchRenderer
this->pageLength = pageLength;
}
std::string renderTemplate(const std::string& tmpl_str);
/**
* Generate the html page with the resutls of the search.
*
* @param mapper The `NameMapper` to use to do the rendering.
* @param library The `Library` to use to look up book details for search results.
May be nullptr. In this case, bookName is not set in the rendered string.
* @return The html string
*/
std::string getHtml();
std::string getHtml(const NameMapper& mapper, const Library* library);
/**
/**
* Generate the xml page with the resutls of the search.
*
* @param mapper The `NameMapper` to use to do the rendering.
* @param library The `Library` to use to look up book details for search results.
May be nullptr. In this case, bookName is not set in the rendered string.
* @return The xml string
*/
std::string getXml();
std::string getXml(const NameMapper& mapper, const Library* library);
protected: // function
std::string renderTemplate(const std::string& tmpl_str, const NameMapper& mapper, const Library *library);
protected:
std::string beautifyInteger(const unsigned int number);
zim::SearchResultSet m_srs;
NameMapper* mp_nameMapper;
Library* mp_library;
std::string searchBookQuery;
std::string searchPattern;
std::string protocolPrefix;

View File

@@ -36,7 +36,7 @@ namespace kiwix
*
* @param library The library to serve.
*/
Server(Library* library, NameMapper* nameMapper=nullptr);
Server(std::shared_ptr<Library> library, std::shared_ptr<NameMapper> nameMapper=nullptr);
virtual ~Server();
@@ -66,8 +66,8 @@ namespace kiwix
std::string getAddress();
protected:
Library* mp_library;
NameMapper* mp_nameMapper;
std::shared_ptr<Library> mp_library;
std::shared_ptr<NameMapper> mp_nameMapper;
std::string m_root = "";
std::string m_addr = "";
std::string m_indexTemplateString = "";

View File

@@ -23,8 +23,12 @@
#include <string>
#include <vector>
#include <map>
#include <cstdint>
namespace kiwix {
typedef std::pair<std::string, std::string> LangNameCodePair;
typedef std::vector<LangNameCodePair> FeedLanguages;
typedef std::vector<std::string> FeedCategories;
/**
* Return the current directory.
@@ -216,5 +220,37 @@ std::map<std::string, std::string> getNetworkInterfaces();
*/
std::string getBestPublicIp();
/** Converts file size to human readable format.
*
* This function will convert a number to its equivalent size using units.
*
* @param number file size in bytes.
* @return a human-readable string representation of the size, e.g., "2.3 KB", "1.8 MB", "5.2 GB".
*/
std::string beautifyFileSize(uint64_t number);
/**
* Load languages stored in an OPDS stream.
*
* @param content the OPDS stream.
* @return vector containing pairs of language code and their corresponding full language name.
*/
FeedLanguages readLanguagesFromFeed(const std::string& content);
/**
* Load categories stored in an OPDS stream .
*
* @param content the OPDS stream.
* @return vector containing category strings.
*/
FeedCategories readCategoriesFromFeed(const std::string& content);
/**
* Retrieve the full language name associated with a given ISO 639-3 language code.
*
* @param lang ISO 639-3 language code.
* @return full language name.
*/
std::string getLanguageSelfName(const std::string& lang);
}
#endif // KIWIX_TOOLS_H

View File

@@ -1,7 +1,7 @@
project('libkiwix', 'cpp',
version : '12.0.0',
version : '13.0.0',
license : 'GPLv3+',
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
compiler = meson.get_compiler('cpp')
@@ -36,7 +36,7 @@ else
endif
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN')
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
endif

View File

@@ -117,11 +117,12 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
m_size = strtoull(ATTR("size"), 0, 0) << 10;
std::string favicon_mimetype = ATTR("faviconMimeType");
if (! favicon_mimetype.empty()) {
const std::string faviconMimeType = ATTR("faviconMimeType");
const std::string faviconBase64EncodedData = ATTR("favicon");
if ( !faviconMimeType.empty() && !faviconBase64EncodedData.empty() ) {
const auto favicon = std::make_shared<Illustration>();
favicon->data = base64_decode(ATTR("favicon"));
favicon->mimeType = favicon_mimetype;
favicon->data = base64_decode(faviconBase64EncodedData);
favicon->mimeType = faviconMimeType;
favicon->url = ATTR("faviconUrl");
m_illustrations.assign(1, favicon);
}

View File

View File

@@ -39,6 +39,8 @@
namespace kiwix
{
namespace
{
@@ -58,6 +60,8 @@ bool booksReferToTheSameArchive(const Book& book1, const Book& book2)
&& book1.getPath() == book2.getPath();
}
} // unnamed namespace
template<typename Key, typename Value>
class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
{
@@ -79,49 +83,8 @@ class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
}
};
} // unnamed namespace
struct Library::Impl
{
struct Entry : Book
{
Library::Revision lastUpdatedRevision = 0;
};
Library::Revision m_revision;
std::map<std::string, Entry> m_books;
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
std::unique_ptr<ArchiveCache> mp_archiveCache;
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
std::unique_ptr<SearcherCache> mp_searcherCache;
std::vector<kiwix::Bookmark> m_bookmarks;
Xapian::WritableDatabase m_bookDB;
unsigned int getBookCount(const bool localBooks, const bool remoteBooks) const;
Impl();
~Impl();
Impl(Impl&& );
Impl& operator=(Impl&& );
};
Library::Impl::Impl()
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
m_bookDB("", Xapian::DB_BACKEND_INMEMORY)
{
}
Library::Impl::~Impl()
{
}
Library::Impl::Impl(Library::Impl&& ) = default;
Library::Impl& Library::Impl::operator=(Library::Impl&& ) = default;
unsigned int
Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const
Library::getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const
{
unsigned int result = 0;
for (auto& pair: m_books) {
@@ -136,50 +99,41 @@ Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const
/* Constructor */
Library::Library()
: mp_impl(new Library::Impl)
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
m_bookDB(new Xapian::WritableDatabase("", Xapian::DB_BACKEND_INMEMORY))
{
}
Library::Library(Library&& other)
: mp_impl(std::move(other.mp_impl))
{
}
Library& Library::operator=(Library&& other)
{
mp_impl = std::move(other.mp_impl);
return *this;
}
/* Destructor */
Library::~Library() = default;
bool Library::addBook(const Book& book)
{
std::lock_guard<std::mutex> lock(m_mutex);
++mp_impl->m_revision;
++m_revision;
/* Try to find it */
updateBookDB(book);
try {
auto& oldbook = mp_impl->m_books.at(book.getId());
auto& oldbook = m_books.at(book.getId());
if ( ! booksReferToTheSameArchive(oldbook, book) ) {
dropCache(book.getId());
}
oldbook.update(book); // XXX: This may have no effect if oldbook is readonly
// XXX: Then m_bookDB will become out-of-sync with
// XXX: the real contents of the library.
oldbook.lastUpdatedRevision = mp_impl->m_revision;
oldbook.lastUpdatedRevision = m_revision;
return false;
} catch (std::out_of_range&) {
auto& newEntry = mp_impl->m_books[book.getId()];
auto& newEntry = m_books[book.getId()];
static_cast<Book&>(newEntry) = book;
newEntry.lastUpdatedRevision = mp_impl->m_revision;
size_t new_cache_size = static_cast<size_t>(std::ceil(mp_impl->getBookCount(true, true)*0.1));
newEntry.lastUpdatedRevision = m_revision;
size_t new_cache_size = static_cast<size_t>(std::ceil(getBookCount_not_protected(true, true)*0.1));
if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) {
mp_impl->mp_archiveCache->setMaxSize(new_cache_size);
mp_archiveCache->setMaxSize(new_cache_size);
}
if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) {
mp_impl->mp_searcherCache->setMaxSize(new_cache_size);
mp_searcherCache->setMaxSize(new_cache_size);
}
return true;
}
@@ -188,15 +142,15 @@ bool Library::addBook(const Book& book)
void Library::addBookmark(const Bookmark& bookmark)
{
std::lock_guard<std::mutex> lock(m_mutex);
mp_impl->m_bookmarks.push_back(bookmark);
m_bookmarks.push_back(bookmark);
}
bool Library::removeBookmark(const std::string& zimId, const std::string& url)
{
std::lock_guard<std::mutex> lock(m_mutex);
for(auto it=mp_impl->m_bookmarks.begin(); it!=mp_impl->m_bookmarks.end(); it++) {
for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) {
if (it->getBookId() == zimId && it->getUrl() == url) {
mp_impl->m_bookmarks.erase(it);
m_bookmarks.erase(it);
return true;
}
}
@@ -206,14 +160,14 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
void Library::dropCache(const std::string& id)
{
mp_impl->mp_archiveCache->drop(id);
mp_impl->mp_searcherCache->drop(id);
mp_archiveCache->drop(id);
mp_searcherCache->drop(id);
}
bool Library::removeBookById(const std::string& id)
{
std::lock_guard<std::mutex> lock(m_mutex);
mp_impl->m_bookDB.delete_document("Q" + id);
m_bookDB->delete_document("Q" + id);
dropCache(id);
// We do not change the cache size here
// Most of the time, the book is remove in case of library refresh, it is
@@ -221,9 +175,9 @@ bool Library::removeBookById(const std::string& id)
// Having a too big cache is not a problem here (or it would have been before)
// (And setMaxSize doesn't actually reduce the cache size, extra cached items
// will be removed in put or getOrPut).
const bool bookWasRemoved = mp_impl->m_books.erase(id) == 1;
const bool bookWasRemoved = m_books.erase(id) == 1;
if ( bookWasRemoved ) {
++mp_impl->m_revision;
++m_revision;
}
return bookWasRemoved;
}
@@ -231,7 +185,7 @@ bool Library::removeBookById(const std::string& id)
Library::Revision Library::getRevision() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return mp_impl->m_revision;
return m_revision;
}
uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
@@ -239,7 +193,7 @@ uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
BookIdCollection booksToRemove;
{
std::lock_guard<std::mutex> lock(m_mutex);
for ( const auto& entry : mp_impl->m_books) {
for ( const auto& entry : m_books) {
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
booksToRemove.push_back(entry.first);
}
@@ -258,7 +212,7 @@ const Book& Library::getBookById(const std::string& id) const
{
// XXX: Doesn't make sense to lock this operation since it cannot
// XXX: guarantee thread-safety because of its return type
return mp_impl->m_books.at(id);
return m_books.at(id);
}
Book Library::getBookByIdThreadSafe(const std::string& id) const
@@ -271,7 +225,7 @@ const Book& Library::getBookByPath(const std::string& path) const
{
// XXX: Doesn't make sense to lock this operation since it cannot
// XXX: guarantee thread-safety because of its return type
for(auto& it: mp_impl->m_books) {
for(auto& it: m_books) {
auto& book = it.second;
if (book.getPath() == path)
return book;
@@ -284,7 +238,7 @@ const Book& Library::getBookByPath(const std::string& path) const
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
{
try {
return mp_impl->mp_archiveCache->getOrPut(id,
return mp_archiveCache->getOrPut(id,
[&](){
auto book = getBookById(id);
if (!book.isPathValid()) {
@@ -301,7 +255,7 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
{
assert(!ids.empty());
try {
return mp_impl->mp_searcherCache->getOrPut(ids,
return mp_searcherCache->getOrPut(ids,
[&](){
std::vector<zim::Archive> archives;
for(auto& id:ids) {
@@ -322,7 +276,7 @@ unsigned int Library::getBookCount(const bool localBooks,
const bool remoteBooks) const
{
std::lock_guard<std::mutex> lock(m_mutex);
return mp_impl->getBookCount(localBooks, remoteBooks);
return getBookCount_not_protected(localBooks, remoteBooks);
}
bool Library::writeToFile(const std::string& path) const
@@ -353,7 +307,7 @@ Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) con
std::lock_guard<std::mutex> lock(m_mutex);
AttributeCounts propValueCounts;
for (const auto& pair: mp_impl->m_books) {
for (const auto& pair: m_books) {
const auto& book = pair.second;
if (book.getOrigId().empty()) {
propValueCounts[(book.*p)()] += 1;
@@ -385,7 +339,7 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
std::lock_guard<std::mutex> lock(m_mutex);
AttributeCounts langsWithCounts;
for (const auto& pair: mp_impl->m_books) {
for (const auto& pair: m_books) {
const auto& book = pair.second;
if (book.getOrigId().empty()) {
for ( const auto& lang : book.getLanguages() ) {
@@ -401,7 +355,7 @@ std::vector<std::string> Library::getBooksCategories() const
std::lock_guard<std::mutex> lock(m_mutex);
std::set<std::string> categories;
for (const auto& pair: mp_impl->m_books) {
for (const auto& pair: m_books) {
const auto& book = pair.second;
const auto& c = book.getCategory();
if ( !c.empty() ) {
@@ -425,12 +379,12 @@ std::vector<std::string> Library::getBooksPublishers() const
const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const
{
if (!onlyValidBookmarks) {
return mp_impl->m_bookmarks;
return m_bookmarks;
}
std::vector<kiwix::Bookmark> validBookmarks;
auto booksId = getBooksIds();
std::lock_guard<std::mutex> lock(m_mutex);
for(auto& bookmark:mp_impl->m_bookmarks) {
for(auto& bookmark:m_bookmarks) {
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
validBookmarks.push_back(bookmark);
}
@@ -443,7 +397,7 @@ Library::BookIdCollection Library::getBooksIds() const
std::lock_guard<std::mutex> lock(m_mutex);
BookIdCollection bookIds;
for (auto& pair: mp_impl->m_books) {
for (auto& pair: m_books) {
bookIds.push_back(pair.first);
}
@@ -482,7 +436,7 @@ void Library::updateBookDB(const Book& book)
}
indexer.index_text(normalizeText(book.getCreator()), 1, "A");
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
indexer.index_text(normalizeText(book.getName()), 1, "XN");
doc.add_term("XN"+normalizeText(book.getName()));
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
@@ -498,7 +452,7 @@ void Library::updateBookDB(const Book& book)
doc.set_data(book.getId());
mp_impl->m_bookDB.replace_document(idterm, doc);
m_bookDB->replace_document(idterm, doc);
}
namespace
@@ -549,25 +503,30 @@ Xapian::Query nameQuery(const std::string& name)
return Xapian::Query("XN" + normalizeText(name));
}
Xapian::Query categoryQuery(const std::string& category)
Xapian::Query multipleParamQuery(const std::string& commaSeparatedList, const std::string& prefix)
{
return Xapian::Query("XC" + normalizeText(category));
Xapian::Query q;
bool firstIteration = true;
for ( const auto& elem : kiwix::split(commaSeparatedList, ",") ) {
const Xapian::Query singleQuery(prefix + normalizeText(elem));
if ( firstIteration ) {
q = singleQuery;
firstIteration = false;
} else {
q = Xapian::Query(Xapian::Query::OP_OR, q, singleQuery);
}
}
return q;
}
Xapian::Query categoryQuery(const std::string& commaSeparatedCategoryList)
{
return multipleParamQuery(commaSeparatedCategoryList, "XC");
}
Xapian::Query langQuery(const std::string& commaSeparatedLanguageList)
{
Xapian::Query q;
bool firstIteration = true;
for ( const auto& lang : kiwix::split(commaSeparatedLanguageList, ",") ) {
const Xapian::Query singleLangQuery("L" + normalizeText(lang));
if ( firstIteration ) {
q = singleLangQuery;
firstIteration = false;
} else {
q = Xapian::Query(Xapian::Query::OP_OR, q, singleLangQuery);
}
}
return q;
return multipleParamQuery(commaSeparatedLanguageList, "L");
}
Xapian::Query publisherQuery(const std::string& publisher)
@@ -642,9 +601,9 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
BookIdCollection bookIds;
std::lock_guard<std::mutex> lock(m_mutex);
Xapian::Enquire enquire(mp_impl->m_bookDB);
Xapian::Enquire enquire(*m_bookDB);
enquire.set_query(query);
const auto results = enquire.get_mset(0, mp_impl->m_books.size());
const auto results = enquire.get_mset(0, m_books.size());
for ( auto it = results.begin(); it != results.end(); ++it ) {
bookIds.push_back(it.get_document().get_data());
}
@@ -658,7 +617,7 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
const auto preliminaryResult = filterViaBookDB(filter);
std::lock_guard<std::mutex> lock(m_mutex);
for(auto id : preliminaryResult) {
if(filter.accept(mp_impl->m_books.at(id))) {
if(filter.accept(m_books.at(id))) {
result.push_back(id);
}
}

View File

@@ -23,64 +23,6 @@ void LibraryDumper::setOpenSearchInfo(int totalResults, int startIndex, int coun
m_count = count;
}
namespace {
std::map<std::string, std::string> iso639_3 = {
{"atj", "atikamekw"},
{"azb", "آذربایجان دیلی"},
{"bcl", "central bikol"},
{"bgs", "tagabawa"},
{"bxr", "буряад хэлэн"},
{"cbk", "chavacano"},
{"cdo", "閩東語"},
{"dag", "Dagbani"},
{"diq", "dimli"},
{"dty", "डोटेली"},
{"eml", "emiliân-rumagnōl"},
{"fbs", "српскохрватски"},
{"hbs", "srpskohrvatski"},
{"ido", "ido"},
{"kbp", "kabɩ"},
{"kld", "Gamilaraay"},
{"lbe", "лакку маз"},
{"lbj", "ལ་དྭགས་སྐད་"},
{"map", "Austronesian"},
{"mhr", "марий йылме"},
{"mnw", "ဘာသာမန်"},
{"myn", "mayan"},
{"nah", "nahuatl"},
{"nai", "north American Indian"},
{"nds", "plattdütsch"},
{"nrm", "bhasa narom"},
{"olo", "livvi"},
{"pih", "Pitcairn-Norfolk"},
{"pnb", "Western Panjabi"},
{"rmr", "Caló"},
{"rmy", "romani shib"},
{"roa", "romance languages"},
{"twi", "twi"},
};
std::once_flag fillLanguagesFlag;
void fillLanguagesMap()
{
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
const ICULanguageInfo lang(*icuLangPtr);
iso639_3.insert({lang.iso3Code(), lang.selfName()});
}
}
std::string getLanguageSelfName(const std::string& lang) {
const auto itr = iso639_3.find(lang);
if (itr != iso639_3.end()) {
return itr->second;
}
return lang;
};
} // unnamed namespace
kainjow::mustache::list LibraryDumper::getCategoryData() const
{
const auto now = gen_date_str();
@@ -101,7 +43,6 @@ kainjow::mustache::list LibraryDumper::getLanguageData() const
{
const auto now = gen_date_str();
kainjow::mustache::list languageData;
std::call_once(fillLanguagesFlag, fillLanguagesMap);
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
const std::string languageCode = langAndBookCount.first;
const int bookCount = langAndBookCount.second;

View File

@@ -27,22 +27,12 @@
namespace kiwix
{
namespace
{
struct NoDelete
{
template<class T> void operator()(T*) {}
};
} // unnamed namespace
////////////////////////////////////////////////////////////////////////////////
// LibraryManipulator
////////////////////////////////////////////////////////////////////////////////
LibraryManipulator::LibraryManipulator(Library* library)
: library(*library)
LibraryManipulator::LibraryManipulator(LibraryPtr library)
: library(library)
{}
LibraryManipulator::~LibraryManipulator()
@@ -50,7 +40,7 @@ LibraryManipulator::~LibraryManipulator()
bool LibraryManipulator::addBookToLibrary(const Book& book)
{
const auto ret = library.addBook(book);
const auto ret = library->addBook(book);
if ( ret ) {
bookWasAddedToLibrary(book);
}
@@ -59,13 +49,13 @@ bool LibraryManipulator::addBookToLibrary(const Book& book)
void LibraryManipulator::addBookmarkToLibrary(const Bookmark& bookmark)
{
library.addBookmark(bookmark);
library->addBookmark(bookmark);
bookmarkWasAddedToLibrary(bookmark);
}
uint32_t LibraryManipulator::removeBooksNotUpdatedSince(Library::Revision rev)
{
const auto n = library.removeBooksNotUpdatedSince(rev);
const auto n = library->removeBooksNotUpdatedSince(rev);
if ( n != 0 ) {
booksWereRemovedFromLibrary();
}
@@ -89,15 +79,15 @@ void LibraryManipulator::booksWereRemovedFromLibrary()
////////////////////////////////////////////////////////////////////////////////
/* Constructor */
Manager::Manager(LibraryManipulator* manipulator):
Manager::Manager(LibraryManipulator manipulator):
writableLibraryPath(""),
manipulator(manipulator, NoDelete())
manipulator(manipulator)
{
}
Manager::Manager(Library* library) :
Manager::Manager(LibraryPtr library) :
writableLibraryPath(""),
manipulator(new LibraryManipulator(library))
manipulator(LibraryManipulator(library))
{
}
@@ -121,7 +111,7 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
if (!trustLibrary && !book.getPath().empty()) {
this->readBookFromPath(book.getPath(), &book);
}
manipulator->addBookToLibrary(book);
manipulator.addBookToLibrary(book);
}
return true;
@@ -166,7 +156,7 @@ bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& url
book.updateFromOpds(entryNode, urlHost);
/* Update the book properties with the new importer */
manipulator->addBookToLibrary(book);
manipulator.addBookToLibrary(book);
}
return true;
@@ -241,7 +231,7 @@ std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
|| (!book.getTitle().empty() && !book.getLanguages().empty()
&& !book.getDate().empty())) {
book.setUrl(url);
manipulator->addBookToLibrary(book);
manipulator.addBookToLibrary(book);
return book.getId();
}
}
@@ -296,7 +286,7 @@ bool Manager::readBookmarkFile(const std::string& path)
bookmark.updateFromXml(node);
manipulator->addBookmarkToLibrary(bookmark);
manipulator.addBookmarkToLibrary(bookmark);
}
return true;
@@ -304,7 +294,7 @@ bool Manager::readBookmarkFile(const std::string& path)
void Manager::reload(const Paths& paths)
{
const auto libRevision = manipulator->getLibrary().getRevision();
const auto libRevision = manipulator.getLibrary()->getRevision();
for (std::string path : paths) {
if (!path.empty()) {
if ( kiwix::isRelativePath(path) )
@@ -316,7 +306,7 @@ void Manager::reload(const Paths& paths)
}
}
manipulator->removeBooksNotUpdatedSince(libRevision);
manipulator.removeBooksNotUpdatedSince(libRevision);
}
}

View File

@@ -17,6 +17,8 @@ kiwix_sources = [
'tools/regexTools.cpp',
'tools/stringTools.cpp',
'tools/networkTools.cpp',
'tools/opdsParsingTools.cpp',
'tools/languageTools.cpp',
'tools/otherTools.cpp',
'tools/archiveTools.cpp',
'kiwixserve.cpp',

View File

@@ -63,7 +63,7 @@ std::string HumanReadableNameMapper::getIdForName(const std::string& name) const
// UpdatableNameMapper
////////////////////////////////////////////////////////////////////////////////
UpdatableNameMapper::UpdatableNameMapper(Library& lib, bool withAlias)
UpdatableNameMapper::UpdatableNameMapper(LibraryPtr lib, bool withAlias)
: library(lib)
, withAlias(withAlias)
{
@@ -72,7 +72,7 @@ UpdatableNameMapper::UpdatableNameMapper(Library& lib, bool withAlias)
void UpdatableNameMapper::update()
{
const auto newNameMapper = new HumanReadableNameMapper(library, withAlias);
const auto newNameMapper = new HumanReadableNameMapper(*library, withAlias);
std::lock_guard<std::mutex> lock(mutex);
nameMapper.reset(newNameMapper);
}

View File

View File

@@ -36,16 +36,9 @@ namespace kiwix
{
/* Constructor */
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
unsigned int start, unsigned int estimatedResultCount)
: SearchRenderer(srs, mapper, nullptr, start, estimatedResultCount)
{}
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
SearchRenderer::SearchRenderer(zim::SearchResultSet srs,
unsigned int start, unsigned int estimatedResultCount)
: m_srs(srs),
mp_nameMapper(mapper),
mp_library(library),
protocolPrefix("zim://"),
searchProtocolPrefix("search://"),
estimatedResultCount(estimatedResultCount),
@@ -164,7 +157,7 @@ kainjow::mustache::data buildPagination(
return pagination;
}
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const NameMapper& nameMapper, const Library* library)
{
const std::string absPathPrefix = protocolPrefix;
// Build the results list
@@ -172,12 +165,12 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
kainjow::mustache::data result;
const std::string zim_id(it.getZimId());
const auto path = mp_nameMapper->getNameForId(zim_id) + "/" + it.getPath();
const auto path = nameMapper.getNameForId(zim_id) + "/" + it.getPath();
result.set("title", it.getTitle());
result.set("absolutePath", absPathPrefix + urlEncode(path));
result.set("snippet", it.getSnippet());
if (mp_library) {
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());
if (library) {
result.set("bookTitle", library->getBookById(zim_id).getTitle());
}
if (it.getWordCount() >= 0) {
result.set("wordCount", kiwix::beautifyInteger(it.getWordCount()));
@@ -222,14 +215,14 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
return ss.str();
}
std::string SearchRenderer::getHtml()
std::string SearchRenderer::getHtml(const NameMapper& mapper, const Library* library)
{
return renderTemplate(RESOURCE::templates::search_result_html);
return renderTemplate(RESOURCE::templates::search_result_html, mapper, library);
}
std::string SearchRenderer::getXml()
std::string SearchRenderer::getXml(const NameMapper& mapper, const Library* library)
{
return renderTemplate(RESOURCE::templates::search_result_xml);
return renderTemplate(RESOURCE::templates::search_result_xml, mapper, library);
}

View File

@@ -29,7 +29,7 @@
namespace kiwix {
Server::Server(Library* library, NameMapper* nameMapper) :
Server::Server(LibraryPtr library, std::shared_ptr<NameMapper> nameMapper) :
mp_library(library),
mp_nameMapper(nameMapper),
mp_server(nullptr)

View File

@@ -19,7 +19,7 @@
#include "internalServer.h"
#ifdef __FreeBSD__
#ifndef _WIN32
#include <netinet/in.h>
#endif
@@ -254,6 +254,11 @@ get_matching_if_none_match_etag(const RequestContext& r, const std::string& etag
}
}
struct NoDelete
{
template<class T> void operator()(T*) {}
};
} // unnamed namespace
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
@@ -406,8 +411,8 @@ public:
};
InternalServer::InternalServer(Library* library,
NameMapper* nameMapper,
InternalServer::InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
std::string addr,
int port,
std::string root,
@@ -433,7 +438,7 @@ InternalServer::InternalServer(Library* library,
m_ipConnectionLimit(ipConnectionLimit),
mp_daemon(nullptr),
mp_library(library),
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper),
mp_nameMapper(nameMapper ? nameMapper : std::shared_ptr<NameMapper>(&defaultNameMapper, NoDelete())),
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
m_customizedResources(new CustomizedResources)
@@ -754,7 +759,8 @@ std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestCo
const kainjow::mustache::object data{
{"enable_toolbar", m_withTaskbar ? "true" : "false" },
{"enable_link_blocking", m_blockExternalLinks ? "true" : "false" },
{"enable_library_button", m_withLibraryButton ? "true" : "false" }
{"enable_library_button", m_withLibraryButton ? "true" : "false" },
{"default_user_language", request.get_user_language() }
};
return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
}
@@ -786,7 +792,7 @@ std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& req
{
const auto url = request.get_url();
const auto urlParts = kiwix::split(url, "/", true, false);
HTMLDumper htmlDumper(mp_library, mp_nameMapper);
HTMLDumper htmlDumper(mp_library.get(), mp_nameMapper.get());
htmlDumper.setRootLocation(m_root);
htmlDumper.setLibraryId(getLibraryId());
auto userLang = request.get_user_language();
@@ -957,7 +963,7 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
const auto pageLength = getSearchPageSize(request);
/* Get the results */
SearchRenderer renderer(search->getResults(start-1, pageLength), mp_nameMapper, mp_library, start,
SearchRenderer renderer(search->getResults(start-1, pageLength), start,
search->getEstimatedMatches());
renderer.setSearchPattern(searchInfo.pattern);
renderer.setSearchBookQuery(searchInfo.bookFilterQuery);
@@ -965,9 +971,17 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
renderer.setSearchProtocolPrefix(m_root + "/search");
renderer.setPageLength(pageLength);
if (request.get_requested_format() == "xml") {
return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8");
return ContentResponse::build(
*this,
renderer.getXml(*mp_nameMapper, mp_library.get()),
"application/rss+xml; charset=utf-8"
);
}
auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8");
auto response = ContentResponse::build(
*this,
renderer.getHtml(*mp_nameMapper, mp_library.get()),
"text/html; charset=utf-8"
);
// XXX: Now this has to be handled by the iframe-based viewer which
// XXX: has to resolve if the book selection resulted in a single book.
/*
@@ -1088,7 +1102,6 @@ const std::string CONTENT_CSP_HEADER =
"sandbox allow-scripts "
"allow-same-origin "
"allow-top-navigation-by-user-activation "
"allow-modals "
"allow-popups "
"allow-forms "

View File

@@ -92,8 +92,8 @@ class OPDSDumper;
class InternalServer {
public:
InternalServer(Library* library,
NameMapper* nameMapper,
InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
std::string addr,
int port,
std::string root,
@@ -178,8 +178,8 @@ class InternalServer {
int m_ipConnectionLimit;
struct MHD_Daemon* mp_daemon;
Library* mp_library;
NameMapper* mp_nameMapper;
LibraryPtr mp_library;
std::shared_ptr<NameMapper> mp_nameMapper;
SearchCache searchCache;
SuggestionSearcherCache suggestionSearcherCache;

View File

@@ -82,7 +82,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper(mp_library, mp_nameMapper);
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
std::vector<std::string> bookIdsToDump;
@@ -164,7 +164,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
{
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
const auto bookIds = search_catalog(request, opdsDumper);
@@ -185,7 +185,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
+ urlNotFoundMsg;
}
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
@@ -198,7 +198,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
{
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
return ContentResponse::build(
@@ -210,7 +210,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
{
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
return ContentResponse::build(

View File

@@ -202,21 +202,12 @@ std::string RequestContext::get_user_language() const
return userlang.lang;
}
bool RequestContext::user_language_comes_from_cookie() const
{
return userlang.selectedBy == UserLanguage::SelectorKind::COOKIE;
}
RequestContext::UserLanguage RequestContext::determine_user_language() const
{
try {
return {UserLanguage::SelectorKind::QUERY_PARAM, get_argument("userlang")};
} catch(const std::out_of_range&) {}
try {
return {UserLanguage::SelectorKind::COOKIE, cookies.at("userlang")};
} catch(const std::out_of_range&) {}
try {
const std::string acceptLanguage = get_header("Accept-Language");
const auto userLangPrefs = parseUserLanguagePreferences(acceptLanguage);

View File

@@ -119,15 +119,12 @@ class RequestContext {
std::string get_user_language() const;
std::string get_requested_format() const;
bool user_language_comes_from_cookie() const;
private: // types
struct UserLanguage
{
enum SelectorKind
{
QUERY_PARAM,
COOKIE,
ACCEPT_LANGUAGE_HEADER,
DEFAULT
};

View File

@@ -387,13 +387,6 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
MHD_add_response_header(response, p.first.c_str(), p.second.c_str());
}
if ( ! request.user_language_comes_from_cookie() ) {
const std::string cookie = "userlang=" + request.get_user_language()
+ ";Path=" + request.get_root_path()
+ ";Max-Age=31536000";
MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cookie.c_str());
}
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;

View File

@@ -0,0 +1,75 @@
#include "tools.h"
#include "stringTools.h"
#include <mutex>
namespace kiwix
{
namespace
{
// These mappings are not provided by the ICU library, any such mappings can be manually added here
std::map<std::string, std::string> iso639_3 = {
{"atj", "atikamekw"},
{"azb", "آذربایجان دیلی"},
{"bcl", "central bikol"},
{"bgs", "tagabawa"},
{"bxr", "буряад хэлэн"},
{"cbk", "chavacano"},
{"cdo", "閩東語"},
{"dag", "Dagbani"},
{"diq", "dimli"},
{"dty", "डोटेली"},
{"eml", "emiliân-rumagnōl"},
{"fbs", "српскохрватски"},
{"fon", "fɔ̀ngbè"},
{"guw", "Gungbe"},
{"hbs", "srpskohrvatski"},
{"ido", "ido"},
{"kbp", "kabɩ"},
{"kld", "Gamilaraay"},
{"lbe", "лакку маз"},
{"lbj", "ལ་དྭགས་སྐད་"},
{"map", "Austronesian"},
{"mhr", "марий йылме"},
{"mnw", "ဘာသာမန်"},
{"myn", "mayan"},
{"nah", "nahuatl"},
{"nai", "north American Indian"},
{"nds", "plattdütsch"},
{"nrm", "bhasa narom"},
{"olo", "livvi"},
{"pih", "Pitcairn-Norfolk"},
{"pnb", "Western Panjabi"},
{"rmr", "Caló"},
{"rmy", "romani shib"},
{"roa", "romance languages"},
{"twi", "twi"},
// ICU for Ubuntu versions <= focal (20.04) returns "" for the language code ""
// unlike the later versions - which returns "und". We map this value to "Undetermined" for a common ground.
{"", "Undetermined"},
};
std::once_flag fillLanguagesFlag;
void fillLanguagesMap()
{
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
const kiwix::ICULanguageInfo lang(*icuLangPtr);
iso639_3.insert({lang.iso3Code(), lang.selfName()});
}
}
} // unnamed namespace
std::string getLanguageSelfName(const std::string& lang)
{
std::call_once(fillLanguagesFlag, fillLanguagesMap);
const auto itr = iso639_3.find(lang);
if (itr != iso639_3.end()) {
return itr->second;
}
return lang;
};
} // namespace kiwix

View File

@@ -0,0 +1,70 @@
#include "tools.h"
#include <pugixml.hpp>
namespace kiwix
{
namespace
{
#define VALUE(name) entryNode.child(name).child_value()
FeedLanguages parseLanguages(const pugi::xml_document& doc)
{
pugi::xml_node feedNode = doc.child("feed");
FeedLanguages langs;
for (pugi::xml_node entryNode = feedNode.child("entry"); entryNode;
entryNode = entryNode.next_sibling("entry")) {
auto title = VALUE("title");
auto code = VALUE("dc:language");
langs.push_back({code, title});
}
return langs;
}
FeedCategories parseCategories(const pugi::xml_document& doc)
{
pugi::xml_node feedNode = doc.child("feed");
FeedCategories categories;
for (pugi::xml_node entryNode = feedNode.child("entry"); entryNode;
entryNode = entryNode.next_sibling("entry")) {
auto title = VALUE("title");
categories.push_back(title);
}
return categories;
}
} // unnamed namespace
FeedLanguages readLanguagesFromFeed(const std::string& content)
{
pugi::xml_document doc;
pugi::xml_parse_result result
= doc.load_buffer((void*)content.data(), content.size());
if (result) {
auto langs = parseLanguages(doc);
return langs;
}
return FeedLanguages();
}
FeedCategories readCategoriesFromFeed(const std::string& content)
{
pugi::xml_document doc;
pugi::xml_parse_result result
= doc.load_buffer((void*)content.data(), content.size());
FeedCategories categories;
if (result) {
categories = parseCategories(doc);
return categories;
}
return categories;
}
} // namespace kiwix

View File

@@ -330,17 +330,19 @@ std::string kiwix::render_template(const std::string& template_str, kainjow::mus
namespace
{
std::string escapeBackslashes(const std::string& s)
std::string escapeForJSON(const std::string& s)
{
std::string es;
es.reserve(s.size());
std::ostringstream oss;
for (char c : s) {
if ( c == '\\' ) {
es.push_back('\\');
oss << "\\\\";
} else if ( unsigned(c) < 0x20U ) {
oss << "\\u" << std::setw(4) << std::setfill('0') << unsigned(c);
} else {
oss << c;
}
es.push_back(c);
}
return es;
return oss.str();
}
std::string makeFulltextSearchSuggestion(const std::string& lang,
@@ -368,10 +370,10 @@ void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
? suggestion.getSnippet()
: suggestion.getTitle();
result.set("label", escapeBackslashes(label));
result.set("value", escapeBackslashes(suggestion.getTitle()));
result.set("label", escapeForJSON(label));
result.set("value", escapeForJSON(suggestion.getTitle()));
result.set("kind", "path");
result.set("path", escapeBackslashes(suggestion.getPath()));
result.set("path", escapeForJSON(suggestion.getPath()));
result.set("first", m_data.is_empty_list());
m_data.push_back(result);
}
@@ -381,8 +383,8 @@ void kiwix::Suggestions::addFTSearchSuggestion(const std::string& uiLang,
{
kainjow::mustache::data result;
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
result.set("label", escapeBackslashes(label));
result.set("value", escapeBackslashes(queryString + " "));
result.set("label", escapeForJSON(label));
result.set("value", escapeForJSON(queryString + " "));
result.set("kind", "pattern");
result.set("first", m_data.is_empty_list());
m_data.push_back(result);

View File

@@ -31,7 +31,6 @@
namespace kiwix
{
std::string beautifyInteger(uint64_t number);
std::string beautifyFileSize(uint64_t number);
void printStringInHexadecimal(const char* s);
void printStringInHexadecimal(icu::UnicodeString s);
void stringReplacement(std::string& str,

View File

@@ -5,8 +5,11 @@ skin/i18n/de.json
skin/i18n/dga.json
skin/i18n/el.json
skin/i18n/en.json
skin/i18n/es.json
skin/i18n/fi.json
skin/i18n/fr.json
skin/i18n/he.json
skin/i18n/hi.json
skin/i18n/hy.json
skin/i18n/ia.json
skin/i18n/it.json
@@ -15,14 +18,19 @@ skin/i18n/ko.json
skin/i18n/ku-latn.json
skin/i18n/lb.json
skin/i18n/mk.json
skin/i18n/ms.json
skin/i18n/nl.json
skin/i18n/nqo.json
skin/i18n/or.json
skin/i18n/pl.json
skin/i18n/ru.json
skin/i18n/sc.json
skin/i18n/sk.json
skin/i18n/skr-arab.json
skin/i18n/sl.json
skin/i18n/sq.json
skin/i18n/sv.json
skin/i18n/te.json
skin/i18n/test.json
skin/i18n/tr.json
skin/i18n/zh-hans.json

View File

@@ -9,7 +9,8 @@ skin/search-icon.svg
skin/iso6391To3.js
skin/isotope.pkgd.min.js
skin/index.js
skin/autoComplete.min.js
skin/autoComplete/autoComplete.min.js
skin/kiwix.css
skin/taskbar.css
skin/index.css
skin/fonts/Poppins.ttf
@@ -42,7 +43,7 @@ templates/no_js_download.html
opensearchdescription.xml
ft_opensearchdescription.xml
catalog_v2_searchdescription.xml
skin/css/autoComplete.css
skin/autoComplete/css/autoComplete.css
skin/favicon/android-chrome-192x192.png
skin/favicon/android-chrome-512x512.png
skin/favicon/apple-touch-icon.png

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,654 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.autoComplete = factory());
}(this, (function () { 'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = it.call(o);
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
var select$1 = function select(element) {
return typeof element === "string" ? document.querySelector(element) : element();
};
var create = function create(tag, options) {
var el = typeof tag === "string" ? document.createElement(tag) : tag;
for (var key in options) {
var val = options[key];
if (key === "inside") {
val.append(el);
} else if (key === "dest") {
select$1(val[0]).insertAdjacentElement(val[1], el);
} else if (key === "around") {
var ref = val;
ref.parentNode.insertBefore(el, ref);
el.append(ref);
if (ref.getAttribute("autofocus") != null) ref.focus();
} else if (key in el) {
el[key] = val;
} else {
el.setAttribute(key, val);
}
}
return el;
};
var getQuery = function getQuery(field) {
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement ? field.value : field.innerHTML;
};
var format = function format(value, diacritics) {
value = value.toString().toLowerCase();
return diacritics ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").normalize("NFC") : value;
};
var debounce = function debounce(callback, duration) {
var timer;
return function () {
clearTimeout(timer);
timer = setTimeout(function () {
return callback();
}, duration);
};
};
var checkTrigger = function checkTrigger(query, condition, threshold) {
return condition ? condition(query) : query.length >= threshold;
};
var mark = function mark(value, cls) {
return create("mark", _objectSpread2({
innerHTML: value
}, typeof cls === "string" && {
"class": cls
})).outerHTML;
};
var configure = (function (ctx) {
var name = ctx.name,
options = ctx.options,
resultsList = ctx.resultsList,
resultItem = ctx.resultItem;
for (var option in options) {
if (_typeof(options[option]) === "object") {
if (!ctx[option]) ctx[option] = {};
for (var subOption in options[option]) {
ctx[option][subOption] = options[option][subOption];
}
} else {
ctx[option] = options[option];
}
}
ctx.selector = ctx.selector || "#" + name;
resultsList.destination = resultsList.destination || ctx.selector;
resultsList.id = resultsList.id || name + "_list_" + ctx.id;
resultItem.id = resultItem.id || name + "_result";
ctx.input = select$1(ctx.selector);
});
var eventEmitter = (function (name, ctx) {
ctx.input.dispatchEvent(new CustomEvent(name, {
bubbles: true,
detail: ctx.feedback,
cancelable: true
}));
});
var search = (function (query, record, options) {
var _ref = options || {},
mode = _ref.mode,
diacritics = _ref.diacritics,
highlight = _ref.highlight;
var nRecord = format(record, diacritics);
record = record.toString();
query = format(query, diacritics);
if (mode === "loose") {
query = query.replace(/ /g, "");
var qLength = query.length;
var cursor = 0;
var match = Array.from(record).map(function (character, index) {
if (cursor < qLength && nRecord[index] === query[cursor]) {
character = highlight ? mark(character, highlight) : character;
cursor++;
}
return character;
}).join("");
if (cursor === qLength) return match;
} else {
var _match = nRecord.indexOf(query);
if (~_match) {
query = record.substring(_match, _match + query.length);
_match = highlight ? record.replace(query, mark(query, highlight)) : record;
return _match;
}
}
});
var getData = function getData(ctx, query) {
return new Promise(function ($return, $error) {
var data;
data = ctx.data;
if (data.cache && data.store) return $return();
return new Promise(function ($return, $error) {
if (typeof data.src === "function") {
return data.src(query).then($return, $error);
}
return $return(data.src);
}).then(function ($await_4) {
try {
ctx.feedback = data.store = $await_4;
eventEmitter("response", ctx);
return $return();
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
});
};
var findMatches = function findMatches(query, ctx) {
var data = ctx.data,
searchEngine = ctx.searchEngine;
var matches = [];
data.store.forEach(function (value, index) {
var find = function find(key) {
var record = key ? value[key] : value;
var match = typeof searchEngine === "function" ? searchEngine(query, record) : search(query, record, {
mode: searchEngine,
diacritics: ctx.diacritics,
highlight: ctx.resultItem.highlight
});
if (!match) return;
var result = {
match: match,
value: value
};
if (key) result.key = key;
matches.push(result);
};
if (data.keys) {
var _iterator = _createForOfIteratorHelper(data.keys),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var key = _step.value;
find(key);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
} else {
find();
}
});
if (data.filter) matches = data.filter(matches);
var results = matches.slice(0, ctx.resultsList.maxResults);
ctx.feedback = {
query: query,
matches: matches,
results: results
};
eventEmitter("results", ctx);
};
var Expand = "aria-expanded";
var Active = "aria-activedescendant";
var Selected = "aria-selected";
var feedback = function feedback(ctx, index) {
ctx.feedback.selection = _objectSpread2({
index: index
}, ctx.feedback.results[index]);
};
var render = function render(ctx) {
var resultsList = ctx.resultsList,
list = ctx.list,
resultItem = ctx.resultItem,
feedback = ctx.feedback;
var matches = feedback.matches,
results = feedback.results;
ctx.cursor = -1;
list.innerHTML = "";
if (matches.length || resultsList.noResults) {
var fragment = new DocumentFragment();
results.forEach(function (result, index) {
var element = create(resultItem.tag, _objectSpread2({
id: "".concat(resultItem.id, "_").concat(index),
role: "option",
innerHTML: result.match,
inside: fragment
}, resultItem["class"] && {
"class": resultItem["class"]
}));
if (resultItem.element) resultItem.element(element, result);
});
list.append(fragment);
if (resultsList.element) resultsList.element(list, feedback);
open(ctx);
} else {
close(ctx);
}
};
var open = function open(ctx) {
if (ctx.isOpen) return;
(ctx.wrapper || ctx.input).setAttribute(Expand, true);
ctx.list.removeAttribute("hidden");
ctx.isOpen = true;
eventEmitter("open", ctx);
};
var close = function close(ctx) {
if (!ctx.isOpen) return;
(ctx.wrapper || ctx.input).setAttribute(Expand, false);
ctx.input.setAttribute(Active, "");
ctx.list.setAttribute("hidden", "");
ctx.isOpen = false;
eventEmitter("close", ctx);
};
var goTo = function goTo(index, ctx) {
var resultItem = ctx.resultItem;
var results = ctx.list.getElementsByTagName(resultItem.tag);
var cls = resultItem.selected ? resultItem.selected.split(" ") : false;
if (ctx.isOpen && results.length) {
var _results$index$classL;
var state = ctx.cursor;
if (index >= results.length) index = 0;
if (index < 0) index = results.length - 1;
ctx.cursor = index;
if (state > -1) {
var _results$state$classL;
results[state].removeAttribute(Selected);
if (cls) (_results$state$classL = results[state].classList).remove.apply(_results$state$classL, _toConsumableArray(cls));
}
results[index].setAttribute(Selected, true);
if (cls) (_results$index$classL = results[index].classList).add.apply(_results$index$classL, _toConsumableArray(cls));
ctx.input.setAttribute(Active, results[ctx.cursor].id);
ctx.list.scrollTop = results[index].offsetTop - ctx.list.clientHeight + results[index].clientHeight + 5;
ctx.feedback.cursor = ctx.cursor;
feedback(ctx, index);
eventEmitter("navigate", ctx);
}
};
var next = function next(ctx) {
goTo(ctx.cursor + 1, ctx);
};
var previous = function previous(ctx) {
goTo(ctx.cursor - 1, ctx);
};
var select = function select(ctx, event, index) {
index = index >= 0 ? index : ctx.cursor;
if (index < 0) return;
ctx.feedback.event = event;
feedback(ctx, index);
eventEmitter("selection", ctx);
close(ctx);
};
var click = function click(event, ctx) {
var itemTag = ctx.resultItem.tag.toUpperCase();
var items = Array.from(ctx.list.querySelectorAll(itemTag));
var item = event.target.closest(itemTag);
if (item && item.nodeName === itemTag) {
select(ctx, event, items.indexOf(item));
}
};
var navigate = function navigate(event, ctx) {
switch (event.keyCode) {
case 40:
case 38:
event.preventDefault();
event.keyCode === 40 ? next(ctx) : previous(ctx);
break;
case 13:
if (!ctx.submit) event.preventDefault();
if (ctx.cursor >= 0) select(ctx, event);
break;
case 9:
if (ctx.resultsList.tabSelect && ctx.cursor >= 0) select(ctx, event);
break;
case 27:
ctx.input.value = "";
close(ctx);
break;
}
};
function start (ctx, q) {
var _this = this;
return new Promise(function ($return, $error) {
var queryVal, condition;
queryVal = q || getQuery(ctx.input);
queryVal = ctx.query ? ctx.query(queryVal) : queryVal;
condition = checkTrigger(queryVal, ctx.trigger, ctx.threshold);
if (condition) {
return getData(ctx, queryVal).then(function ($await_2) {
try {
if (ctx.feedback instanceof Error) return $return();
findMatches(queryVal, ctx);
if (ctx.resultsList) render(ctx);
return $If_1.call(_this);
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
} else {
close(ctx);
return $If_1.call(_this);
}
function $If_1() {
return $return();
}
});
}
var eventsManager = function eventsManager(events, callback) {
for (var element in events) {
for (var event in events[element]) {
callback(element, event);
}
}
};
var addEvents = function addEvents(ctx) {
var events = ctx.events;
var run = debounce(function () {
return start(ctx);
}, ctx.debounce);
var publicEvents = ctx.events = _objectSpread2({
input: _objectSpread2({}, events && events.input)
}, ctx.resultsList && {
list: events ? _objectSpread2({}, events.list) : {}
});
var privateEvents = {
input: {
input: function input() {
run();
},
keydown: function keydown(event) {
navigate(event, ctx);
},
blur: function blur() {
close(ctx);
}
},
list: {
mousedown: function mousedown(event) {
event.preventDefault();
},
click: function click$1(event) {
click(event, ctx);
}
}
};
eventsManager(privateEvents, function (element, event) {
if (!ctx.resultsList && event !== "input") return;
if (publicEvents[element][event]) return;
publicEvents[element][event] = privateEvents[element][event];
});
eventsManager(publicEvents, function (element, event) {
ctx[element].addEventListener(event, publicEvents[element][event]);
});
};
var removeEvents = function removeEvents(ctx) {
eventsManager(ctx.events, function (element, event) {
ctx[element].removeEventListener(event, ctx.events[element][event]);
});
};
function init (ctx) {
var _this = this;
return new Promise(function ($return, $error) {
var placeHolder, resultsList, parentAttrs;
placeHolder = ctx.placeHolder;
resultsList = ctx.resultsList;
parentAttrs = {
role: "combobox",
"aria-owns": resultsList.id,
"aria-haspopup": true,
"aria-expanded": false
};
create(ctx.input, _objectSpread2(_objectSpread2({
"aria-controls": resultsList.id,
"aria-autocomplete": "both"
}, placeHolder && {
placeholder: placeHolder
}), !ctx.wrapper && _objectSpread2({}, parentAttrs)));
if (ctx.wrapper) ctx.wrapper = create("div", _objectSpread2({
around: ctx.input,
"class": ctx.name + "_wrapper"
}, parentAttrs));
if (resultsList) ctx.list = create(resultsList.tag, _objectSpread2({
dest: [resultsList.destination, resultsList.position],
id: resultsList.id,
role: "listbox",
hidden: "hidden"
}, resultsList["class"] && {
"class": resultsList["class"]
}));
addEvents(ctx);
if (ctx.data.cache) {
return getData(ctx).then(function ($await_2) {
try {
return $If_1.call(_this);
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
}
function $If_1() {
eventEmitter("init", ctx);
return $return();
}
return $If_1.call(_this);
});
}
function extend (autoComplete) {
var prototype = autoComplete.prototype;
prototype.init = function () {
init(this);
};
prototype.start = function (query) {
start(this, query);
};
prototype.unInit = function () {
if (this.wrapper) {
var parentNode = this.wrapper.parentNode;
parentNode.insertBefore(this.input, this.wrapper);
parentNode.removeChild(this.wrapper);
}
removeEvents(this);
};
prototype.open = function () {
open(this);
};
prototype.close = function () {
close(this);
};
prototype.goTo = function (index) {
goTo(index, this);
};
prototype.next = function () {
next(this);
};
prototype.previous = function () {
previous(this);
};
prototype.select = function (index) {
select(this, null, index);
};
prototype.search = function (query, record, options) {
return search(query, record, options);
};
}
function autoComplete(config) {
this.options = config;
this.id = autoComplete.instances = (autoComplete.instances || 0) + 1;
this.name = "autoComplete";
this.wrapper = 1;
this.threshold = 1;
this.debounce = 0;
this.resultsList = {
position: "afterend",
tag: "ul",
maxResults: 5
};
this.resultItem = {
tag: "li"
};
configure(this);
extend.call(this, autoComplete);
init(this);
}
return autoComplete;
})));

View File

@@ -1,3 +1,4 @@
/* Modified from https://github.com/TarekRaafat/autoComplete.js (version 10.2.6)*/
.autoComplete_wrapper {
display: inline-block;
position: relative;

View File

@@ -69,30 +69,19 @@ function $t(msgId, params={}) {
}
}
function getCookie(cookieName) {
const name = cookieName + "=";
let result;
decodeURIComponent(document.cookie).split('; ').forEach(val => {
if (val.indexOf(name) === 0) {
result = val.substring(name.length);
}
});
return result;
}
const DEFAULT_UI_LANGUAGE = 'en';
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
function getUserLanguage() {
return new URLSearchParams(window.location.search).get('userlang')
|| getCookie('userlang')
|| window.localStorage.getItem('userlang')
|| viewerSettings.defaultUserLanguage
|| DEFAULT_UI_LANGUAGE;
}
function setUserLanguage(lang, callback) {
setPermanentGlobalCookie('userlang', lang);
window.localStorage.setItem('userlang', lang);
Translations.load(lang);
Translations.whenReady(callback);
}

View File

@@ -11,5 +11,8 @@
"library-button-text": "স্বাগত পাতায় চলুন",
"home-button-text": "'{{BOOK_TITLE}}'-এর প্রধান পাতায় চলুন",
"searchbox-tooltip": "'{{BOOK_TITLE}}' অনুসন্ধান করুন",
"search": "অনুসন্ধান"
"search": "অনুসন্ধান",
"welcome-to-kiwix-server": "কিউইক্স সার্ভারে স্বাগতম",
"download-links-title": "বই ডাউনলোড করুন",
"preview-book": "প্রাকদর্শন"
}

View File

@@ -1,20 +1,57 @@
{
"@metadata": {
"authors": [
"IMayBeABitShy",
"Lucas Werkmeister",
"ThisCarthing"
]
},
"name": "Deutsch",
"suggest-full-text-search": "enthält '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Buch nicht gefunden: {{BOOK_NAME}}",
"too-many-books": "Zu viele Bücher angefragt ({{NB_BOOKS}}), die Beschränkung liegt bei {{LIMIT}}",
"no-book-found": "Keine Bücher entsprechen den Auswahlkriterien",
"url-not-found": "Die angeforderte URL \"{{url}}\" konnte auf diesem Server nicht gefunden werden.",
"suggest-search": "Führe eine Volltextsuche nach <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> durch",
"random-article-failure": "Hoppla! Konnte keinen zufälligen Artikel auswählen :(",
"invalid-raw-data-type": "{{DATATYPE}} ist keine gültige Anfrage für unverarbeiteten Inhalt",
"no-value-for-arg": "Kein Wert für den Parameter {{ARGUMENT}} gegeben",
"no-query": "Keine Suchanfrage gegeben.",
"raw-entry-not-found": "Eintrag {{ENTRY}} des Typs {{DATATYPE}} konnte nicht gefunden werden.",
"400-page-title": "Ungültige Anfrage",
"400-page-heading": "Ungültige Anfrage",
"404-page-title": "Inhalt nicht gefunden",
"404-page-heading": "Nicht gefunden",
"500-page-title": "Interner Server-Fehler",
"500-page-heading": "Interner Server-Fehler",
"fulltext-search-unavailable": "Die Volltestsuche steht nicht zur Verfügung.",
"no-search-results": "Die Volltextsuche ist für diesen Inhalt nicht verfügbar.",
"library-button-text": "Zur Willkommensseite gehen",
"home-button-text": "Zur Hauptseite von '{{BOOK_TITLE}}' gehen",
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen"
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen",
"confusion-of-tongues": "Zwei oder mehr Bücher unterschiedlicher Sprachen werden durchsucht, was zu unübersichtlichen Ergebnissen führen kann.",
"welcome-page-overzealous-filter": "Keine Ergebnisse gefunden. Möchten Sie den <a href=\"{{URL}}\">Filter zurücksetzen</a>?",
"powered-by-kiwix-html": "Angetrieben durch &nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Suchen",
"book-filtering-all-categories": "Alle Kategorien",
"book-filtering-all-languages": "Alle Sprachen",
"count-of-matching-books": "{{COUNT}} Bücher",
"download": "Herunterladen",
"direct-download-link-text": "Direkt",
"direct-download-alt-text": "direkt herunterladen",
"hash-download-link-text": "Sha256 Hash",
"hash-download-alt-text": "Hash herunterladen",
"magnet-link-text": "Magnet Link",
"magnet-alt-text": "Magnet Link herunterladen",
"torrent-download-link-text": "Torrent-Datei",
"torrent-download-alt-text": "Torrent herunterladen",
"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",
"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"
}

View File

@@ -1,10 +1,11 @@
{
"@metadata": {
"authors": [
"Alhaji Yakubu"
"Alhaji Yakubu",
"Amire80"
]
},
"welcome-page-overzealous-filter": "Duoro kyebe. E na boɔra ka fo <a href=\"?lang=\"></a>",
"welcome-page-overzealous-filter": "Duoro kyebe. <a href=\"{{URL}}\">E na boɔra ka fo</a>?",
"search": "Bo",
"book-filtering-all-categories": "Zagre zaa",
"book-filtering-all-languages": "Kɔkɔrɛɛ zaa",
@@ -15,7 +16,6 @@
"hash-download-alt-text": "Tage bonmannaa",
"magnet-link-text": "Kurimaraa sobie",
"magnet-alt-text": "Tage kurimaraa",
"library-opds-feed": "Gamadie OPDS diibu",
"filter-by-tag": "Guy yi kpuli {{TAG}}",
"stop-filtering-by-tag": "Bare gyɛɛbo kpuli {{TAG}}"
}

View File

@@ -1,10 +1,11 @@
{
"@metadata": {
"authors": [
"Kelson",
"Norhorn"
]
},
"welcome-page-overzealous-filter": "Κανένα αποτέλεσμα. Θέλετε να <a href=\"?lang=\">επαναφέρετε το φίλτρο</a>;",
"welcome-page-overzealous-filter": "Κανένα αποτέλεσμα. Θέλετε να <a href=\"{{URL}}\">επαναφέρετε το φίλτρο</a>;",
"powered-by-kiwix-html": "Με την υποστήριξη by&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Αναζήτηση",
"book-filtering-all-categories": "Όλες οι κατηγορίες",

57
static/skin/i18n/es.json Normal file
View File

@@ -0,0 +1,57 @@
{
"@metadata": {
"authors": [
"Fitoschido",
"Ovruni",
"SpikeShroom",
"Vis4valentine"
]
},
"name": "español",
"suggest-full-text-search": "que contenga \"{{{SEARCH_TERMS}}}\"...",
"no-such-book": "No existe el libro: {{BOOK_NAME}}",
"too-many-books": "Demasiadas solicitudes de libros ({{NB_BOOKS}}) donde el límite es {{LIMIT}}",
"no-book-found": "Ningún libro coincide con los criterios de selección.",
"url-not-found": "La URL solicitada \"{{url}}\" no se encontró en este servidor.",
"suggest-search": "Haga una búsqueda de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "¡Ups! Error al elegir un artículo aleatorio :(",
"invalid-raw-data-type": "{{DATATYPE}} no es una solicitud válida de contenido en crudo.",
"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}}",
"400-page-title": "Solicitud inválida",
"400-page-heading": "Solicitud inválida",
"404-page-title": "Contenido no encontrado",
"404-page-heading": "No encontrado",
"500-page-title": "Error interno del servidor",
"500-page-heading": "Error interno del servidor",
"fulltext-search-unavailable": "Búsqueda de texto completo no disponible",
"no-search-results": "El motor de búsqueda de texto completo no está disponible para este contenido.",
"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",
"searchbox-tooltip": "Buscar '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Dos o más libros en diferentes idiomas participarían en la búsqueda, lo que puede llevar a resultados confusos.",
"welcome-page-overzealous-filter": "Sin resultados. ¿Quieres <a href=\"{{URL}}\">restablecer el filtro</a> ?",
"powered-by-kiwix-html": "Desarrollado por&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Buscar",
"book-filtering-all-categories": "Todas las categorías",
"book-filtering-all-languages": "Todos los idiomas",
"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",
"magnet-link-text": "Enlace magnético",
"magnet-alt-text": "Descargar link magnético",
"torrent-download-link-text": "Archivo de torrent",
"torrent-download-alt-text": "descargar torrent",
"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"
}

30
static/skin/i18n/fi.json Normal file
View File

@@ -0,0 +1,30 @@
{
"@metadata": {
"authors": [
"MITO",
"Nike",
"Pyscowicz"
]
},
"name": "suomi",
"suggest-full-text-search": "sisältää '{{{SEARCH_TERMS}}}'...",
"url-not-found": "Pyydettyä URL-osoitetta \"{{url}}\" ei löytynyt tältä palvelimelta.",
"400-page-title": "Virheellinen pyyntö",
"400-page-heading": "Virheellinen pyyntö",
"404-page-title": "Sisältöä ei löytynyt",
"404-page-heading": "Ei löytynyt",
"500-page-title": "Sisäinen palvelinvirhe",
"500-page-heading": "Sisäinen palvelinvirhe",
"library-button-text": "Siirry tervetulosivulle",
"home-button-text": "Siirry kirjan '{{BOOK_TITLE}}' etusivulle",
"random-page-button-text": "Siirry satunnaiselle sivulle",
"searchbox-tooltip": "Hae '{{BOOK_TITLE}}'",
"search": "Hae",
"book-filtering-all-categories": "Kaikki luokat",
"book-filtering-all-languages": "Kaikki kielet",
"download": "Lataa",
"torrent-download-link-text": "Torrent-tiedosto",
"filter-by-tag": "Suodata tunnisteen ”{{TAG}}” mukaan",
"download-links-title": "Lataa kirja",
"preview-book": "Esikatsele"
}

View File

@@ -2,11 +2,12 @@
"@metadata": {
"authors": [
"Gomoko",
"Stephane",
"Thibaut120094",
"Verdy p"
]
},
"name": "français",
"name": "Français",
"suggest-full-text-search": "contenant « {{{SEARCH_TERMS}}} »...",
"no-such-book": "Aucun livre avec ce nom: {{BOOK_NAME}}",
"too-many-books": "Trop de livres demandés ({{NB_BOOKS}}) alors que la limite est de {{LIMIT}}",
@@ -31,7 +32,7 @@
"random-page-button-text": "Aller à une page sélectionnée aléatoirement",
"searchbox-tooltip": "Rechercher « {{BOOK_TITLE}} »",
"confusion-of-tongues": "Deux livres ou plus dans des langues différentes participeraient à la recherche, ce qui pourrait conduire à des résultats confus.",
"welcome-page-overzealous-filter": "Aucun résultat. Souhaitez-vous <a href=\"?lang=\">réinitialiser le filtre</a>?",
"welcome-page-overzealous-filter": "Aucun résultat. Souhaitez-vous <a href=\"{{URL}}\">réinitialiser le filtre</a>?",
"powered-by-kiwix-html": "Propulsé par <a href=\"https://kiwix.org/\">Kiwix</a>",
"search": "Rechercher",
"book-filtering-all-categories": "Toutes les catégories",
@@ -46,7 +47,12 @@
"magnet-alt-text": "télécharger le lien Magnet",
"torrent-download-link-text": "Fichier torrent",
"torrent-download-alt-text": "télécharger le torrent",
"library-opds-feed": "Flux OPDS de la bibliothèque",
"library-opds-feed-all-entries": "Flux OPDS de la bibliothèque Toutes les entrées",
"filter-by-tag": "Filtrer par la balise « {{TAG}} »",
"stop-filtering-by-tag": "Arrêter le filtrage par la balise « {{TAG}} »"
"stop-filtering-by-tag": "Arrêter le filtrage par la balise « {{TAG}} »",
"library-opds-feed-parameterised": "Flux OPDS de la bibliothèque Entrées correspondant à {{#LANG}}:\n ▪ Langue: {{LANG}} {{/LANG}}{{#CATEGORY}}\n ▪ Catégorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n ▪ Étiquette: {{TAG}} {{/TAG}}{{#Q}}\n ▪ Requête: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bienvenue sur le Serveur Kiwix",
"download-links-heading": "Liens de téléchargement pour <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Télécharger le livre",
"preview-book": "Aperçu"
}

View File

@@ -30,7 +30,7 @@
"random-page-button-text": "מעבר לדף שנבחר אקראית",
"searchbox-tooltip": "חיפוש \"{{BOOK_TITLE}}\"",
"confusion-of-tongues": "שני ספרים או יותר בשפות שונות ישתתפו בחיפוש, מה שעלול להוביל לתוצאות מבלבלות.",
"welcome-page-overzealous-filter": "אין תוצאות. האם <a href=\"?lang=\">לאפס את המסנן</a>?",
"welcome-page-overzealous-filter": "אין תוצאות. האם <a href=\"{{URL}}\">לאפס את המסנן</a>?",
"powered-by-kiwix-html": "מופעל על־ידי&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "חיפוש",
"book-filtering-all-categories": "כל הקטגוריות",
@@ -45,7 +45,12 @@
"magnet-alt-text": "הורדת magnet",
"torrent-download-link-text": "קובץ טורנט",
"torrent-download-alt-text": "הורדת טורנט",
"library-opds-feed": "הזנת OPDS של ספרייה",
"library-opds-feed-all-entries": "הזנת ספריית OPDS - כל הרשומות",
"filter-by-tag": "סינון לפי התג \"{{TAG}}\"",
"stop-filtering-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": "ברוך בואך לשרת קיוויקס",
"download-links-heading": "הורדת קישורים עבור <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "הורדת ספר",
"preview-book": "תצוגה מקדימה"
}

56
static/skin/i18n/hi.json Normal file
View File

@@ -0,0 +1,56 @@
{
"@metadata": {
"authors": [
"Abijeet Patro",
"Juuz0"
]
},
"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}} कच्ची सामग्री के लिए वैध अनुरोध नहीं है।",
"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": "आंतरिक सर्वर त्रुटि",
"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": "विभिन्न भाषाओं की दो या दो से अधिक पुस्तकें खोज में भाग लेंगी, जिससे भ्रमित करने वाले परिणाम मिल सकते हैं।",
"welcome-page-overzealous-filter": "कोई परिणाम नहीं। क्या आप <a href=\"{{URL}}\">फ़िल्टर रीसेट करना</a> चाहेंगे?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">किविक्स</a> द्वारा संचालित",
"search": "खोजें",
"book-filtering-all-categories": "सब वर्ग",
"book-filtering-all-languages": "सभी भाषाएँ",
"count-of-matching-books": "{{COUNT}} पुस्तक/पुस्तकें",
"download": "डाउनलोड",
"direct-download-link-text": "प्रत्यक्ष",
"direct-download-alt-text": "प्रत्यक्षत: डाउनलोड",
"hash-download-link-text": "शा256 हैश",
"hash-download-alt-text": "हैश डाउनलोड करें",
"magnet-link-text": "MAGNET लिंक",
"magnet-alt-text": "MAGNET डाउनलोड करें",
"torrent-download-link-text": "टोरेंट फ़ाइल",
"torrent-download-alt-text": "टोरेंट डाउनलोड करें",
"library-opds-feed-all-entries": "लाइब्रेरी ओपीडीएस फ़ीड - सभी प्रविष्टियाँ",
"filter-by-tag": "टैग \"{{TAG}}\" द्वारा फ़िल्टर करें",
"stop-filtering-by-tag": "\"{{TAG}}\" टैग द्वारा फ़िल्टर करना बंद करें",
"library-opds-feed-parameterised": "लाइब्रेरी ओपीडीएस फ़ीड - मिलान वाली प्रविष्टियाँ {{#LANG}}\nभाषा: {{LANG}} {/LANG}}{{#CATEGORY}}\nश्रेणी: {{CATEGORY}} {/CATEGORY}} {{#TAG}}\nटैग: {{TAG}} {{/TAG}}{{#Q}}\nक्वेरी: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
"download-links-title": "पुस्तक डाउनलोड करें",
"preview-book": "पूर्वावलोकन"
}

View File

@@ -1,10 +1,36 @@
{
"@metadata": {
"authors": [
"Kelson",
"McDutchie"
]
},
"welcome-page-overzealous-filter": "Nulle resultato. Vole tu <a href=\"?lang=\">reinitialisar le filtro</a>?",
"name": "interlingua",
"suggest-full-text-search": "continente '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Necun tal libro: {{BOOK_NAME}}",
"too-many-books": "Troppo de libros demandate ({{NB_BOOKS}}); le limite es {{LIMIT}}",
"no-book-found": "Necun libro corresponde al criterios de selection",
"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.",
"no-value-for-arg": "Necun valor fornite pro le argumento {{ARGUMENT}}",
"no-query": "Necun consulta fornite.",
"raw-entry-not-found": "Non pote trovar le entrata {{ENTRY}} del typo {{DATATYPE}}",
"400-page-title": "Requesta invalide",
"400-page-heading": "Requesta invalide",
"404-page-title": "Contento non trovate",
"404-page-heading": "Non trovate",
"500-page-title": "Error interne del servitor",
"500-page-heading": "Error interne del servitor",
"fulltext-search-unavailable": "Le recerca in texto complete es indisponibile",
"no-search-results": "Le motor de recerca in texto complete non es disponibile pro iste contento.",
"library-button-text": "Ir al pagina de benvenita",
"home-button-text": "Ir al pagina principal de ''{{BOOK_TITLE}}",
"random-page-button-text": "Ir a un pagina seligite aleatorimente",
"searchbox-tooltip": "Cercar '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Duo o plus libros in differente linguas participarea in le recerca, lo que pote menar a resultatos confuse.",
"welcome-page-overzealous-filter": "Nulle resultato. Vole tu <a href=\"{{URL}}\">reinitialisar le filtro</a>?",
"powered-by-kiwix-html": "Actionate per&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Cercar",
"book-filtering-all-categories": "Tote le categorias",
@@ -19,7 +45,12 @@
"magnet-alt-text": "ligamine \"magnet\" de discargamento",
"torrent-download-link-text": "File Torrent",
"torrent-download-alt-text": "discargar Torrent",
"library-opds-feed": "Fluxo OPDS del bibliotheca",
"library-opds-feed-all-entries": "Fluxo OPDS del bibliotheca Tote le entratas",
"filter-by-tag": "Filtrar per etiquetta \"{{TAG}}\"",
"stop-filtering-by-tag": "Non plus filtrar per etiquetta \"{{TAG}}\""
"stop-filtering-by-tag": "Non plus filtrar per etiquetta \"{{TAG}}\"",
"library-opds-feed-parameterised": "Fluxo OPDS del bibliotheca Entratas correspondente a {{#LANG}}\nLingua {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtiquetta: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Benvenite al servitor Kiwix",
"download-links-heading": "Discargar ligamines pro <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Discargar libro",
"preview-book": "Previsualisation"
}

View File

@@ -2,12 +2,13 @@
"@metadata": {
"authors": [
"Albano",
"Beta16"
"Beta16",
"McDutchie"
]
},
"name": "italiano",
"suggest-full-text-search": "contenente '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Nessun libro del genere: {{BOOK_NAME}}",
"no-such-book": "Nessun libro con questo nome: {{BOOK_NAME}}",
"too-many-books": "Troppi libri richiesti ({{NB_BOOKS}}) dove il limite è {{LIMIT}}",
"no-book-found": "Nessun libro corrisponde ai criteri di selezione",
"url-not-found": "L'URL richiesto \"{{url}}\" non è stato trovato in questo server.",
@@ -27,5 +28,7 @@
"book-filtering-all-categories": "Tutte le categorie",
"book-filtering-all-languages": "Tutte le lingue",
"count-of-matching-books": "{{COUNT}} libro/i",
"download": "Scarica"
"download": "Scarica",
"download-links-title": "Scarica libro",
"preview-book": "Anteprima"
}

View File

@@ -28,5 +28,6 @@
"magnet-link-text": "マグネットリンク",
"magnet-alt-text": "マグネットをダウンロード",
"torrent-download-link-text": "Torrentファイル",
"torrent-download-alt-text": "Torrentをダウンロード"
"torrent-download-alt-text": "Torrentをダウンロード",
"preview-book": "プレビュー"
}

View File

@@ -14,5 +14,6 @@
"500-page-title": "내부 서버 오류",
"500-page-heading": "내부 서버 오류",
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
"random-page-button-text": "무작위로 선택된 문서로 이동"
"random-page-button-text": "무작위로 선택된 문서로 이동",
"preview-book": "미리 보기"
}

View File

@@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Kelson",
"Robby",
"Volvox"
]
@@ -16,11 +17,12 @@
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
"random-page-button-text": "Gitt op eng zoufälleg gewielte Säit",
"searchbox-tooltip": "No '{{BOOK_TITLE}}' sichen",
"welcome-page-overzealous-filter": "Kee Resultat. Wëllt Dir <a href=\"?lang=\">de Filter zrécksetzen</a>?",
"welcome-page-overzealous-filter": "Kee Resultat. Wëllt Dir <a href=\"{{URL}}\">de Filter zrécksetzen</a>?",
"search": "Sichen",
"book-filtering-all-categories": "All Kategorien",
"book-filtering-all-languages": "All Sproochen",
"count-of-matching-books": "{{COUNT}} Buch/Bicher",
"download": "Eroflueden",
"direct-download-link-text": "Direkt"
"direct-download-link-text": "Direkt",
"download-links-title": "Buch eroflueden"
}

View File

@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
"Bjankuloski06"
"Bjankuloski06",
"Kelson"
]
},
"name": "македонски",
@@ -29,7 +30,7 @@
"random-page-button-text": "Оди на случајно избрана страница",
"searchbox-tooltip": "Пребарај го „{{BOOK_TITLE}}“",
"confusion-of-tongues": "Во пребарувањето ќе учествуваат две или повеќе книги на различни јазици, што може да довете до збунувачки исход.",
"welcome-page-overzealous-filter": "Нема исход. Дали би сакале да го <a href=\"?lang=\">поништите филтерот</a>?",
"welcome-page-overzealous-filter": "Нема исход. Дали би сакале да го <a href=\"{{URL}}\">поништите филтерот</a>?",
"powered-by-kiwix-html": "Овозможено од&nbsp;<a href=\"https://kiwix.org\">Кивикс</a>",
"search": "Пребарај",
"book-filtering-all-categories": "Сите категории",
@@ -44,7 +45,12 @@
"magnet-alt-text": "преземи магнет",
"torrent-download-link-text": "Торентна податотека",
"torrent-download-alt-text": "преземи торент",
"library-opds-feed": "Библиотечен OPDS-тековник",
"library-opds-feed-all-entries": "Библиотечен тековник на OPDS — Сите ставки",
"filter-by-tag": "Филтрирај по ознаката „{{TAG}}“",
"stop-filtering-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": "Добре дојдовте на Опслужувачот на Кивикс",
"download-links-heading": "Врски за преземање на <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Преземи книга",
"preview-book": "Преглед"
}

20
static/skin/i18n/ms.json Normal file
View File

@@ -0,0 +1,20 @@
{
"@metadata": {
"authors": [
"Tofeiku"
]
},
"name": "Bahasa Melayu",
"404-page-heading": "Tidak Dijumpai",
"500-page-title": "Ralat Pelayan Dalaman",
"500-page-heading": "Ralat Pelayan Dalaman",
"library-button-text": "Pergi ke laman selamat datang",
"searchbox-tooltip": "Cari '{{BOOK_TITLE}}'",
"search": "Cari",
"book-filtering-all-categories": "Semua kategori",
"book-filtering-all-languages": "Semua bahasa",
"download": "Muat turun",
"direct-download-link-text": "Langsung",
"direct-download-alt-text": "muat turun langsung",
"download-links-title": "Muat turun buku"
}

View File

@@ -1,15 +1,37 @@
{
"@metadata": {
"authors": [
"Kelson",
"McDutchie",
"Vistaus"
]
},
"too-many-books": "Er zijn teveel boeken opgevraagd ({{NB_BOOKS}}). Het limiet is {{LIMIT}}.",
"name": "Nederlands",
"suggest-full-text-search": "bevat {{{SEARCH_TERMS}}}’…",
"no-such-book": "Boek bestaat niet: {{BOOK_NAME}}",
"too-many-books": "Er zijn teveel boeken opgevraagd ({{NB_BOOKS}}). De limiet is {{LIMIT}}.",
"no-book-found": "Er zijn geen boeken die overeenkomen met de zoekcriteria",
"url-not-found": "De opgevraagde URL “{{url}}” is niet gevonden op deze server.",
"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.",
"no-value-for-arg": "Er is geen waarde opgegeven bij {{ARGUMENT}}",
"no-query": "Er is geen zoekterm opgegeven.",
"welcome-page-overzealous-filter": "Geen resultaat. Wilt u <a href=\"?lang=\">het filter resetten</a>?",
"raw-entry-not-found": "Kan het {{DATATYPE}}-item {{ENTRY}} niet vinden",
"400-page-title": "Ongeldig verzoek",
"400-page-heading": "Ongeldig verzoek",
"404-page-title": "Inhoud niet gevonden",
"404-page-heading": "Niet gevonden",
"500-page-title": "Interne serverfout",
"500-page-heading": "Interne serverfout",
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
"library-button-text": "Naar de welkomstpagina",
"home-button-text": "Naar de hoofdpagina van {{BOOK_TITLE}}",
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
"searchbox-tooltip": "Naar {{BOOK_TITLE}} zoeken",
"confusion-of-tongues": "Er zouden twee of meer boeken in verschillende talen deelnemen aan de zoekopdracht, wat tot verwarrende resultaten kan leiden.",
"welcome-page-overzealous-filter": "Geen resultaat. Wilt u <a href=\"{{URL}}\">het filter resetten</a>?",
"powered-by-kiwix-html": "Mogelijk gemaakt door <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Zoeken",
"book-filtering-all-categories": "Alle categorieën",
@@ -24,7 +46,12 @@
"magnet-alt-text": "magnet-link van de download",
"torrent-download-link-text": "Torrent-bestand",
"torrent-download-alt-text": "torrent downloaden",
"library-opds-feed": "OPDS-feed van de bibliotheek",
"filter-by-tag": "Filteren op tag \"{{TAG}}\"",
"stop-filtering-by-tag": "Stoppen met filteren op tag \"{{TAG}}\""
"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}}”",
"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"
}

View File

@@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Amire80",
"Lancine.kounfantoh.fofana"
]
},
@@ -29,5 +30,21 @@
"random-page-button-text": "ߕߊ߯ ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ߫ ߛߎߥߊ߲ߘߌߣߍ߲ ߠߎ߬ ߞߊ߲߬",
"searchbox-tooltip": "ߕߌߙߌ߲ߠߌ߲ {{BOOK_TITLE}}",
"confusion-of-tongues": "ߞߊ߬ߝߊ߫ ߝߌ߬ߟߊ߬ ߥߟߊ߫ ߦߙߌߞߊ ߞߊ߲߫ ߜߘߍ ߟߎ߬ ߘߐ߫߸ ߏ߬ ߟߎ߫ ߘߌߣߊ߬ ߕߘߍ߬ ߢߌߣߌ߲ߠߌ߲ ߘߐ߫߸ ߡߍ߲ ߠߎ߬ ߛߌ߫ ߞߣߐ߬ߝߟߌ ߟߊߘߏ߲߬ ߠߊ߫ ߞߐߝߟߌ ߘߐ߫.",
"welcome-page-overzealous-filter": "ߞߐߝߟߌ߫ ߕߴߦߋ߲߬. ߊ߬ ߝߐ߫ ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ <a href=\"?lang=\">ߛߍ߲ߛߍ߲ߟߊ߲ ߘߐߛߌ߰ ߕߎ߲߯</a>؟"
"welcome-page-overzealous-filter": "ߞߐߝߟߌ߫ ߕߴߦߋ߲߬. ߊ߬ ߝߐ߫ ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ <a href=\"{{URL}}\">ߛߍ߲ߛߍ߲ߟߊ߲ ߘߐߛߌ߰ ߕߎ߲߯</a>؟",
"search": "ߢߌߣߌ߲ߠߌ߲",
"book-filtering-all-categories": "ߦߌߟߡߊ ߟߎ߬ ߓߍ߯",
"book-filtering-all-languages": "ߞߊ߲ ߠߎ߬ ߓߍ߯",
"count-of-matching-books": "ߞߊ߬ߝߊ {{COUNT}}(ߟߎ߫)",
"download": "ߟߊ߬ߖߌ߰ߒ߬ߞߎ߲߬ߠߌ߲",
"direct-download-link-text": "ߒߕߋߟߋ߲ߡߊ߬",
"direct-download-alt-text": "ߟߊ߬ߖߌ߰ߒ߬ߞߎ߲߬ߠߌ߲߫ ߝߊ߲ߞߢߊ",
"hash-download-alt-text": "ߤߊߛ߭ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
"magnet-link-text": "ߡߊߢߍߕ ߛߘߌ߬ߜߋ߲",
"magnet-alt-text": "ߡߊߢߍߕ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
"torrent-download-link-text": "ߕߏߙߍ߲ߕ ߞߐߕߐ߮",
"torrent-download-alt-text": "ߕߏߙߍ߲ߕ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
"welcome-to-kiwix-server": "ߌ ߣߌ߫ ߛߣߍ߫ ߞߥߌߞߛ ߡߊߛߐߟߊ߲ ߞߣߐ߫",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> ߛߘߌ߬ߜߋ߲ ߠߊߖߌ߰ ߌ ߞߎ߲߬",
"download-links-title": "ߞߊ߬ߝߊ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
"preview-book": "ߊ߬ ߘߐߜߍ߫ ߡߎߣߎ߲߬"
}

55
static/skin/i18n/or.json Normal file
View File

@@ -0,0 +1,55 @@
{
"@metadata": {
"authors": [
"Gouri"
]
},
"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}} for</a> ପାଇଁ ଏକ ସମ୍ପୂର୍ଣ୍ଣ ପାଠ ସନ୍ଧାନ କର |",
"random-article-failure": "ଓହୋ! ଏକ ଅନିୟମିତ ପ୍ରବନ୍ଧ ବାଛିବାରେ ବିଫଳ :(",
"invalid-raw-data-type": "{{DATATYPE}} କଞ୍ଚା ବିଷୟବସ୍ତୁ ପାଇଁ ଏକ ବ valid ଧ ଅନୁରୋଧ ନୁହେଁ |",
"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": "ଆନ୍ତରିକ ସର୍ଭର ତୃଟି",
"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": "ବିଭିନ୍ନ ଭାଷାରେ ଦୁଇ କିମ୍ବା ଅଧିକ ପୁସ୍ତକ ସର୍ଚ୍ଚରେ ଅଂଶଗ୍ରହଣ କରିବେ ଯାହା ବିଭ୍ରାନ୍ତିକର ଫଳାଫଳ ଆଣିପାରେ |",
"welcome-page-overzealous-filter": "କୌଣସି ଫଳାଫଳ ନାହିଁ | ଆପଣ <a href=\"{{URL}}\">ଫିଲ୍ଟର ପୁନ res ସେଟ୍</a> କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">କିୱିକ୍ସ</a> ଦ୍ୱାରା ଚାଳିତ |",
"search": "ଖୋଜନ୍ତୁ",
"book-filtering-all-categories": "ସମସ୍ତ ବର୍ଗ |",
"book-filtering-all-languages": "ସମସ୍ତ ଭାଷା",
"count-of-matching-books": "{{COUNT}} ପୁସ୍ତକ (ଗୁଡିକ)",
"download": "ଡାଉନଲୋଡ଼",
"direct-download-link-text": "ସିଧାସଳଖ |",
"direct-download-alt-text": "ସିଧାସଳଖ ଡାଉନଲୋଡ୍ |",
"hash-download-link-text": "Sha256 ହ୍ୟାସ୍ |",
"hash-download-alt-text": "ହ୍ୟାସ୍ ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
"magnet-link-text": "ଚୁମ୍ବକୀୟ ଲିଙ୍କ୍",
"magnet-alt-text": "ଚୁମ୍ବକ ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
"torrent-download-link-text": "ଟୋରେଣ୍ଟ ଫାଇଲ୍ |",
"torrent-download-alt-text": "torrent ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
"library-opds-feed-all-entries": "ଲାଇବ୍ରେରୀ OPDS ଫିଡ୍ - ସମସ୍ତ ଏଣ୍ଟ୍ରିଗୁଡିକ |",
"filter-by-tag": "ଟ୍ୟାଗ୍ ଦ୍ୱାରା ଫିଲ୍ଟର୍ \"{{TAG}}\"",
"stop-filtering-by-tag": "\"${{TAG}}\" ଟ୍ୟାଗ୍ ଦ୍ୱାରା ଫିଲ୍ଟର କରିବା ବନ୍ଦ କରନ୍ତୁ |",
"library-opds-feed-parameterised": "ଲାଇବ୍ରେରୀ OPDS ଫିଡ୍ - ଏଣ୍ଟ୍ରିଗୁଡିକ {{#LANG}}! N! ଭାଷା! ${{LANG}} ! {#TAG}} Category: ${{CATEGORY}} ଟ୍ୟାଗ୍: ${{TAG}} {{/ TAG}} {{# Q}}! ପ୍ରଶ୍ନ: {{Q}} {{/ Q}}",
"welcome-to-kiwix-server": "Kiwix ସର୍ଭରକୁ ସ୍ୱାଗତ |",
"download-links-heading": "<b><i>${{BOOK_TITLE}} for</i></b> ପାଇଁ ଲିଙ୍କ୍ ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
"download-links-title": "ବହି ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
"preview-book": "ପୂର୍ବରୂପ"
}

View File

@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
"Strebski"
"Strebski",
"WaldiSt"
]
},
"name": "Polski",
@@ -23,5 +24,8 @@
"library-button-text": "Przejdź do strony powitalnej",
"home-button-text": "Przejdź do głównej strony '{{BOOK_TITLE}}'",
"random-page-button-text": "Przejdź do losowo wybranej strony",
"searchbox-tooltip": "Szukaj '{{BOOK_TITLE}}'"
"searchbox-tooltip": "Szukaj '{{BOOK_TITLE}}'",
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
"download-links-title": "Pobierz książkę",
"preview-book": "Podgląd"
}

View File

@@ -45,9 +45,9 @@
"magnet-alt-text": "Hint for the icon of a magnet link",
"torrent-download-link-text": "Link text for downloading the torrent file",
"torrent-download-alt-text": "Hint for the icon of torrent download",
"library-opds-feed-all-entries": "Hint for the library OPDS feed for all entries",
"filter-by-tag": "Hint for a link that would load results filtered by a single tag",
"stop-filtering-by-tag": "Tooltip for the button that cancels filtering by tag",
"library-opds-feed-all-entries": "Hint for the library OPDS feed for all entries",
"library-opds-feed-parameterised": "Hint for the library OPDS feed for filtered entries",
"welcome-to-kiwix-server": "Title shown in browser's title bar/page tab",
"download-links-heading": "Heading for no-js download page",

View File

@@ -4,7 +4,9 @@
"Fenixs-ru",
"Kareyac",
"Okras",
"Pacha Tchernof"
"Pacha Tchernof",
"Razno0",
"Smavrina"
]
},
"name": "русский",
@@ -32,7 +34,23 @@
"random-page-button-text": "Перейти на случайно выбранную страницу",
"searchbox-tooltip": "Искать '{{BOOK_TITLE}}'",
"confusion-of-tongues": "В поиске будут участвовать две или более книг на разных языках, что может привести к запутанным результатам.",
"powered-by-kiwix-html": "При поддержке&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Найти",
"book-filtering-all-categories": "Все категории",
"book-filtering-all-languages": "Все языки",
"download": "Скачать"
"count-of-matching-books": "{{COUNT}} книг(и)",
"download": "Скачать",
"direct-download-alt-text": "прямая загрузка",
"hash-download-link-text": "Хэш Sha256",
"hash-download-alt-text": "скачать хэш",
"magnet-link-text": "Магнитная ссылка",
"torrent-download-link-text": "Торрент-файл",
"torrent-download-alt-text": "скачать торрент",
"library-opds-feed-all-entries": "Канал библиотеки OPDS  все записи",
"filter-by-tag": "Фильтровать по тегу \"{{TAG}}\"",
"stop-filtering-by-tag": "Прекратить фильтрацию по тегу \"{{TAG}}\"",
"welcome-to-kiwix-server": "Добро пожаловать на сервер Kiwix",
"download-links-heading": "Ссылки для скачивания <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Скачать книгу",
"preview-book": "Предпросмотр"
}

View File

@@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Kelson",
"L2212"
]
},
@@ -28,5 +29,28 @@
"home-button-text": "Bae a sa pàgina printzipale de '{{BOOK_TITLE}}'",
"random-page-button-text": "Bae a una pàgina seletzionada a manera casuale",
"searchbox-tooltip": "Chirca '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Duos o prus libros in limbas diferentes diant pigare parte a sa chirca, cosa chi diat pòdere causare resurtados confusionosos."
"confusion-of-tongues": "Duos o prus libros in limbas diferentes diant pigare parte a sa chirca, cosa chi diat pòdere causare resurtados confusionosos.",
"welcome-page-overzealous-filter": "Perunu resurtadu. Boles <a href=\"{{URL}}\">resetare su filtru</a>?",
"powered-by-kiwix-html": "Alimentadu dae&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Chirca",
"book-filtering-all-categories": "Totu sas categorias",
"book-filtering-all-languages": "Totu sas limbas",
"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",
"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",
"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}}\"",
"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>",
"download-links-title": "Iscàrriga su libru",
"preview-book": "Antiprima"
}

View File

@@ -0,0 +1,26 @@
{
"@metadata": {
"authors": [
"Saraiki"
]
},
"name": "سرائیکی",
"400-page-title": "غلط ارداس",
"400-page-heading": "غلط ارداس",
"404-page-title": "مواد کائنی لبھیا",
"404-page-heading": "کائنی لبھا",
"500-page-title": "اندرلا سرور نقص",
"500-page-heading": "اندرلا سرور نقص",
"search": "ڳولو",
"book-filtering-all-categories": "ساریاں ونکیاں",
"book-filtering-all-languages": "ساریاں زباناں",
"count-of-matching-books": "{{COUNT}} کتاب(اں)",
"download": "ڈاؤن لوڈ",
"direct-download-link-text": "ڈائرکٹ",
"direct-download-alt-text": "ڈائرکٹ ڈاؤن لوڈ",
"hash-download-alt-text": "ڈاؤن لوڈ ہیش",
"torrent-download-link-text": "ٹورنٹ فائل",
"torrent-download-alt-text": "ٹورںٹ ݙاؤن لوڈ کرو",
"download-links-title": "کتاب ڈاؤن لوڈ کرو",
"preview-book": "پیشگی ݙکھالا"
}

View File

@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
"Eleassar"
"Eleassar",
"Kelson"
]
},
"name": "slovenščina",
@@ -29,7 +30,7 @@
"random-page-button-text": "Pojdite na naključno izbrano stran",
"searchbox-tooltip": "Poiščite »{{BOOK_TITLE}}«",
"confusion-of-tongues": "V iskanju bi bili uporabljeni dve ali več knjig v različnih jezikih, kar lahko pripelje do nejasnih zadetkov.",
"welcome-page-overzealous-filter": "Ni zadetkov. Želite <a href=\"?lang=\">ponastaviti filter</a>?",
"welcome-page-overzealous-filter": "Ni zadetkov. Želite <a href=\"{{URL}}\">ponastaviti filter</a>?",
"powered-by-kiwix-html": "Omogoča <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Išči",
"book-filtering-all-categories": "Vse kategorije",
@@ -44,7 +45,12 @@
"magnet-alt-text": "prenesi magnet",
"torrent-download-link-text": "Torrent datoteka",
"torrent-download-alt-text": "prenesi torrent",
"library-opds-feed": "Vir OPDS knjižnice",
"library-opds-feed-all-entries": "Knjižnični vir OPDS Vsi vnosi",
"filter-by-tag": "Filtriraj po oznaki »{{TAG}}«",
"stop-filtering-by-tag": "Ustavi filtriranje po oznaki »{{TAG}}«"
"stop-filtering-by-tag": "Ustavi filtriranje po oznaki »{{TAG}}«",
"library-opds-feed-parameterised": "Knjižnični vir OPDS vnosi, ki se ujemajo z {{#LANG}}\nJezik: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategorija: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nOznaka: {{TAG}} {{/TAG}}{{#Q}}\nPoizvedba: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Pozdravljeni na strežniku Kiwix",
"download-links-heading": "Povezave za prenos za <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Prenesi knjigo",
"preview-book": "Predogled"
}

55
static/skin/i18n/sq.json Normal file
View File

@@ -0,0 +1,55 @@
{
"@metadata": {
"authors": [
"Besnik b"
]
},
"name": "Shqip",
"suggest-full-text-search": "që përmban '{{{SEARCH_TERMS}}}'…",
"no-such-book": "Ska libër të tillë: {{BOOK_NAME}}",
"too-many-books": "U kërkuan shumë libra ({{NB_BOOKS}}), teksa kufiri është {{LIMIT}}",
"no-book-found": "Ska libër me përputhje me kriteret e përzgjedhjes",
"url-not-found": "URL “{{url}}” e kërkuar su gjet në këtë shërbyes.",
"suggest-search": "Bëni një kërkim të plotë teksti për <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oh! Su arrit të merrej një artikull kuturu :(",
"invalid-raw-data-type": "{{DATATYPE}} sështë varg i vlefshëm kërkimi për lëndë të papërpunuar.",
"no-value-for-arg": "Su dha vlerë për argumentin {{ARGUMENT}}",
"no-query": "Su dha varg kërkimi.",
"raw-entry-not-found": "Sgjendet dot zëri {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Kërkesë e pavlefshme",
"400-page-heading": "Kërkesë e pavlefshme",
"404-page-title": "Su gjet lëndë",
"404-page-heading": "Su Gjet",
"500-page-title": "Gabim i Brendshëm Shërbyesi",
"500-page-heading": "Gabim i Brendshëm Shërbyesi",
"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ë.",
"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",
"searchbox-tooltip": "Kërko në '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Kërkimi do të merrej me dy ose më tepër libra në gjuhë të ndryshme, çka mund të sjellë përfundime të ngatërruara.",
"welcome-page-overzealous-filter": "Ska përfundime. Do të donit të <a href=\"{{URL}}\">riujdisni filtrimin</a>?",
"powered-by-kiwix-html": "Bazuar në&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Kërko",
"book-filtering-all-categories": "Krejt kategoritë",
"book-filtering-all-languages": "Krejt gjuhët",
"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",
"magnet-link-text": "Lidhje Magnet",
"magnet-alt-text": "shkarko magnetin",
"torrent-download-link-text": "Kartelë Torrent",
"torrent-download-alt-text": "shkarko torrent-in",
"library-opds-feed-all-entries": "Prurje OPDS Biblioteke - Krejt zërat",
"filter-by-tag": "Filtroji sipas etiketës “{{TAG}}”",
"stop-filtering-by-tag": "Resht së filtruari sipas etiketë “{{TAG}}”",
"library-opds-feed-parameterised": "Prurje OPDS Biblioteke - zëra që kanë përputhje me {{#LANG}}\nGjuhë: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtiketë: {{TAG}} {{/TAG}}{{#Q}}\nVarg Kërkimi: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Mirë se vini në Shërbyesin Kiwix",
"download-links-heading": "Lidhje shkarkimi për <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Shkarkoje librin",
"preview-book": "Paraparje"
}

View File

@@ -30,5 +30,28 @@
"home-button-text": "Gå till huvudsidan för \"{{BOOK_TITLE}}\"",
"random-page-button-text": "Gå till en slumpmässigt utvald sida",
"searchbox-tooltip": "Sök efter \"{{BOOK_TITLE}}\"",
"confusion-of-tongues": "Två eller fler böcker på olika språk skulle delta i sökningen, vilket kan ge förvirrande resultat."
"confusion-of-tongues": "Två eller fler böcker på olika språk skulle delta i sökningen, vilket kan ge förvirrande resultat.",
"welcome-page-overzealous-filter": "Inga resultat. Vill du <a href=\"{{URL}}\">återställa filtret</a>?",
"powered-by-kiwix-html": "Drivs av&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Sök",
"book-filtering-all-categories": "Alla kategorier",
"book-filtering-all-languages": "Alla språk",
"count-of-matching-books": "{{COUNT}} böcker",
"download": "Ladda ned",
"direct-download-link-text": "Direkt",
"direct-download-alt-text": "direktnedladdning",
"hash-download-link-text": "Sha256-hash",
"hash-download-alt-text": "ladda ned hash",
"magnet-link-text": "Magnetlänk",
"magnet-alt-text": "ladda ned magnet",
"torrent-download-link-text": "Torrent-fil",
"torrent-download-alt-text": "ladda ned torrent",
"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}}\"",
"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"
}

58
static/skin/i18n/te.json Normal file
View File

@@ -0,0 +1,58 @@
{
"@metadata": {
"authors": [
"Amire80",
"Chaduvari",
"Rishitha 1238",
"V Bhavya"
]
},
"name": "ఇంగ్లీషు",
"suggest-full-text-search": "'{{{SEARCH_TERMS}}}'ని కలిగి ఉంది...",
"no-such-book": "అలాంటి పుస్తకం లేదు: {{BOOK_NAME}}",
"too-many-books": "పరిమితి {{LIMIT}} ఉన్న చాలా పుస్తకాలు అభ్యర్థించబడ్డాయి ({{NB_BOOKS}})",
"no-book-found": "ఎంపిక ప్రమాణాలకు ఏ పుస్తకం సరిపోలలేదు",
"url-not-found": "అభ్యర్థించిన URL \"{{url}}\" ఈ సర్వర్‌లో కనుగొనబడలేదు.",
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> కోసం పూర్తి వచన శోధన చేయండి",
"random-article-failure": "అయ్యో! యాదృచ్ఛిక కథనాన్ని ఎంచుకోవడంలో విఫలమైంది :(",
"invalid-raw-data-type": "ముడి కంటెంట్ కోసం {{DATATYPE}} చెల్లుబాటు అయ్యే అభ్యర్థన కాదు.",
"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": "అంతర్గత సర్వర్ లోపం",
"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": "వివిధ భాషల్లోని రెండు లేదా అంతకంటే ఎక్కువ పుస్తకాలు శోధనలో పాల్గొంటాయి, ఇది గందరగోళ ఫలితాలకు దారితీయవచ్చు.",
"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": "నేరుగా డౌన్లోడ్",
"hash-download-link-text": "షా256 హాష్",
"hash-download-alt-text": "హాష్‌ని డౌన్‌లోడ్ చేయండి",
"magnet-link-text": "మాగ్నెట్ లింక్",
"magnet-alt-text": "అయస్కాంతాన్ని డౌన్‌లోడ్ చేయండి",
"torrent-download-link-text": "టోరెంట్ ఫైల్",
"torrent-download-alt-text": "టొరెంట్‌ని డౌన్‌లోడ్ చేయండి",
"library-opds-feed-all-entries": "లైబ్రరీ OPDS ఫీడ్ - అన్ని ఎంట్రీలు",
"filter-by-tag": "\"{{TAG}}\" ట్యాగ్ ద్వారా ఫిల్టర్ చేయండి",
"stop-filtering-by-tag": "\"{{TAG}}\" ట్యాగ్ ద్వారా ఫిల్టర్ చేయడాన్ని ఆపివేయి",
"library-opds-feed-parameterised": "లైబ్రరీ OPDS ఫీడ్ - ఎంట్రీలు సరిపోలే {{#LANG}}\nభాష: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nట్యాగ్: {{TAG}} {{/TAG}}{{#Q}}\nప్రశ్న: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Kiwix సర్వర్‌కి స్వాగతం",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> కోసం లింక్‌లను డౌన్‌లోడ్ చేయండి",
"download-links-title": "పుస్తకాన్ని డౌన్‌లోడ్ చేయండి",
"preview-book": "ప్రివ్యూ"
}

View File

@@ -2,7 +2,9 @@
"@metadata": {
"authors": [
"GuoPC",
"StarrySky"
"StarrySky",
"Sunai",
"XtexChooser"
]
},
"name": "英语",
@@ -12,5 +14,12 @@
"404-page-heading": "未找到",
"500-page-title": "内部服务器错误",
"500-page-heading": "内部服务器错误",
"library-button-text": "前往欢迎页面"
"library-button-text": "前往欢迎页面",
"search": "搜索",
"book-filtering-all-categories": "所有分类",
"book-filtering-all-languages": "所有语言",
"download": "下载",
"magnet-link-text": "磁力链接",
"torrent-download-link-text": "种子文件",
"preview-book": "预览"
}

View File

@@ -1,6 +1,7 @@
{
"@metadata": {
"authors": [
"Kelson",
"Kly",
"Winston Sung"
]
@@ -30,7 +31,7 @@
"random-page-button-text": "前往隨機選取頁面",
"searchbox-tooltip": "在{{BOOK_TITLE}}搜尋",
"confusion-of-tongues": "搜索裡有加入兩本或更多不同語言的書籍,這可能會導致混淆結果。",
"welcome-page-overzealous-filter": "沒有結果。您想要<a href=\"?lang=\">重新設定篩選</a>嗎?",
"welcome-page-overzealous-filter": "沒有結果。您想要<a href=\"{{URL}}\">重新設定篩選</a>嗎?",
"powered-by-kiwix-html": "由 <a href=\"https://kiwix.org\">Kiwix</a> 提供技術支援",
"search": "搜尋",
"book-filtering-all-categories": "所有分類",
@@ -45,7 +46,12 @@
"magnet-alt-text": "下載 magnet",
"torrent-download-link-text": "Torrent 檔案",
"torrent-download-alt-text": "下載 torrent",
"library-opds-feed": "圖書館 OPDS 訊息來源",
"library-opds-feed-all-entries": "圖書館 OPDS 摘要 - 所有項目",
"filter-by-tag": "依標籤「{{TAG}}」篩選",
"stop-filtering-by-tag": "停止依標籤「{{TAG}}」篩選"
"stop-filtering-by-tag": "停止依標籤「{{TAG}}」篩選",
"library-opds-feed-parameterised": "圖書館 OPDS 摘要 - 項目符合 {{#LANG}}\n語言{{LANG}} {{/LANG}}{{#CATEGORY}}\n分類{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n標籤{{TAG}} {{/TAG}}{{#Q}}\n查詢{{Q}} {{/Q}}",
"welcome-to-kiwix-server": "歡迎來到 Kiwix 伺服器",
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
"download-links-title": "下載書籍",
"preview-book": "預覽"
}

View File

@@ -1,29 +1,7 @@
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
}
html {
font-size: 62.5%;
}
body {
position: relative;
box-sizing: border-box;
}
::selection {
background-color: #00b4e4;
color: white;
}
.kiwixNav {
background-color: #f4f6f8;
width: 100%;
padding: 20px;
padding: 10px 20px;
position: sticky;
top: 0;
z-index: 3;
@@ -59,10 +37,10 @@ body {
background-image: none;
border-radius: 1px;
width: 195px;
height: 35px;
height: 30px;
flex: 1;
color: black;
padding: 7px 10px 10px;
padding: 0px 10px 0px 10px;
cursor: pointer;
}
@@ -74,7 +52,7 @@ body {
position: relative;
display: flex;
width: 231px;
height: 35px;
height: 30px;
line-height: 3;
background: #909090;
overflow: hidden;
@@ -104,7 +82,7 @@ body {
}
.kiwixSearch {
height: 35px;
height: 30px;
width: 231px;
border-radius: 10px;
border: solid 1px #b5b2b2;
@@ -118,7 +96,7 @@ body {
.kiwixButton {
margin: 0 17px;
height: 35px;
height: 30px;
width: 100px;
border-radius: 10px;
color: white;
@@ -162,29 +140,6 @@ body {
font-weight: bolder;
}
#uiLanguageSelector {
display: none;
}
#uiLanguageSelector .modal {
height: 140px;
}
#uiLanguageSelector .modal-heading {
height: 40%;
}
#uiLanguageSelector .modal-content #ui_language {
font-size: 1.6rem;
width: 100%;
}
#uiLanguageSelectorButton {
margin: 0 12px 0 0;
float: right;
cursor: pointer;
}
.book__list {
position: relative;
margin: 0 auto;
@@ -325,58 +280,6 @@ body {
-webkit-box-orient: vertical;
}
.modal-wrapper {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
background-color: rgba(0, 0, 0, 30%);
}
.modal {
color: #444343;
height: 280px;
width: 250px;
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
}
.modal-heading {
background-color: #f0f0f0;
height: 20%;
width: 100%;
border-bottom: 1px solid #ececec;
display: grid;
grid-template-columns: 3fr 1fr;
}
.modal-title {
display: flex;
font-size: 15px;
align-items: center;
padding-left: 20px;
font-family: poppins;
}
.modal-close-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
padding: 20px;
}
.modal-content div {
width: 100%;
height: 40px;
@@ -469,7 +372,7 @@ body {
width: auto;
}
.feedLogo, #uiLanguageSelectorButton {
.feedLogo {
height: 30px;
float: right;
}

View File

@@ -46,7 +46,7 @@
window.modalUILanguageSelector.close();
const s = document.getElementById("ui_language");
const lang = s.options[s.selectedIndex].value;
setPermanentGlobalCookie('userlang', lang);
localStorage.setItem('userlang', lang);
window.location.reload();
}
@@ -197,6 +197,58 @@
}
}
function makeURLSearchString(params, keysToURIEncode) {
let output = '';
for (const [key, value] of params.entries()) {
let finalValue = (keysToURIEncode.indexOf(key) >= 0) ? encodeURIComponent(value) : value;
output += `&${key}=${finalValue}`;
}
return output;
}
/* hack for library.kiwix.org magnet links (created by MirrorBrain)
See https://github.com/kiwix/container-images/issues/242 */
async function getFixedMirrorbrainMagnet(magnetLink) {
// parse as query parameters
const params = new URLSearchParams(
magnetLink.replaceAll('&amp;', '&').replace(/^magnet:/, ''));
const zimUrl = params.get('as'); // as= is fallback URL
// download metalink to build list of mirrored URLs
let mirrorUrls = [];
const metalink = await fetch(`${zimUrl}.meta4`).then(response => {
return response.ok ? response.text() : '';
}).catch((_error) => '');
if (metalink) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(metalink, "application/xml");
doc.querySelectorAll("url").forEach((node) => {
if (node.hasAttribute("priority")) { // ensures its a mirror link
mirrorUrls.push(node.innerHTML);
}
});
} catch (err) {
// not a big deal, magnet will only contain primary URL
console.debug(`Failed to parse mirror links for ${zimUrl}`);
}
}
// set webseed (ws=) URL to primary download URL (redirects to mirror)
params.set('ws', zimUrl);
// if we got metalink mirror URLs, append them all
if (mirrorUrls) {
mirrorUrls.forEach((url) => {
params.append('ws', url);
});
}
params.set('xs', `${zimUrl}.torrent`); // adding xs= to point to torrent URL
return 'magnet:?' + makeURLSearchString(params, ['ws', 'as', 'dn', 'xs', 'tr']);
}
async function getMagnetLink(downloadLink) {
const magnetUrl = downloadLink + '.magnet';
const controller = new AbortController();
@@ -204,6 +256,9 @@
const magnetLink = await fetch(magnetUrl, { signal: controller.signal }).then(response => {
return response.ok ? response.text() : '';
}).catch((_error) => '');
if (magnetLink) {
return await getFixedMirrorbrainMagnet(magnetLink);
}
return magnetLink;
}
@@ -585,11 +640,6 @@
setInterval(updateNavVisibilityState, 250);
};
// required by i18n.js:setUserLanguage()
window.setPermanentGlobalCookie = function(name, value) {
document.cookie = `${name}=${value};path=${root};max-age=31536000`;
}
window.onload = () => { setUserLanguage(getUserLanguage(), onload); }
})();

107
static/skin/kiwix.css Normal file
View File

@@ -0,0 +1,107 @@
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
}
html {
font-size: 62.5%;
}
body {
position: relative;
box-sizing: border-box;
}
::selection {
background-color: #00b4e4;
color: white;
}
.modal-wrapper {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
background-color: rgba(0, 0, 0, 30%);
}
.modal {
color: #444343;
height: 280px;
width: 250px;
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
}
.modal-heading {
background-color: #f0f0f0;
height: 20%;
width: 100%;
border-bottom: 1px solid #ececec;
display: grid;
grid-template-columns: 3fr 1fr;
}
.modal-title {
display: flex;
font-size: 15px;
align-items: center;
padding-left: 20px;
font-family: poppins;
}
.modal-close-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
padding: 20px;
}
#uiLanguageSelector {
display: none;
}
#uiLanguageSelector .modal {
height: 140px;
}
#uiLanguageSelector .modal-heading {
height: 40%;
}
#uiLanguageSelector .modal-content #ui_language {
font-size: 1.6rem;
width: 100%;
}
#uiLanguageSelectorButton {
margin: 0px 12px;
float: right;
cursor: pointer;
height: 30px;
}
@font-face {
font-family: "poppins";
src: url("../skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");
}
@font-face {
font-family: "roboto";
src: url("../skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype");
}

View File

@@ -15,14 +15,26 @@ const uiLanguages = [
"English": "en"
},
{
"français": "fr"
"español": "es"
},
{
"suomi": "fi"
},
{
"Français": "fr"
},
{
"עברית": "he"
},
{
"हिन्दी": "hi"
},
{
"Հայերեն": "hy"
},
{
"interlingua": "ia"
},
{
"italiano": "it"
},
@@ -41,9 +53,18 @@ const uiLanguages = [
{
"македонски": "mk"
},
{
"Bahasa Melayu": "ms"
},
{
"Nederlands": "nl"
},
{
"ߒߞߏ": "nqo"
},
{
"ଓଡ଼ିଆ": "or"
},
{
"Polski": "pl"
},
@@ -56,12 +77,21 @@ const uiLanguages = [
{
"slovenčina": "sk"
},
{
"سرائیکی": "skr-arab"
},
{
"slovenščina": "sl"
},
{
"Shqip": "sq"
},
{
"Svenska": "sv"
},
{
"ఇంగ్లీషు": "te"
},
{
"Türkçe": "tr"
},

View File

@@ -3,8 +3,9 @@
transition: 0.3s;
width: 100%;
box-sizing: border-box;
background: #e3e3e3;
background: #f4f6f8;
border-bottom: 1px solid #aaa;
display: flex;
}
#kiwixtoolbar>a {
@@ -21,8 +22,9 @@
}
.kiwixsearch {
font-size: 1.6rem;
position: relative;
height: 26px;
height: 30px;
width: 100%;
left: 0;
margin-bottom: 0;
@@ -41,6 +43,7 @@
.kiwix .kiwix_centered {
max-width: 720px;
width: 100%;
margin: 0 auto;
}
@@ -48,9 +51,8 @@
display: none;
}
#kiwix_button_show_toggle:checked~label~.kiwix_button_cont,
#kiwix_button_show_toggle:checked~label~.kiwix_button_cont>a {
display: block;
display: inline-block;
}
#kiwix_button_show_toggle:not(:checked)~label~.kiwix_button_cont {
@@ -59,12 +61,12 @@
label[for="kiwix_button_show_toggle"] {
display: inline-block;
height: 26px;
height: 30px;
}
label[for="kiwix_button_show_toggle"] img {
transition: 0.1s;
height: 26px;
height: 30px;
}
#kiwix_button_show_toggle:checked~label img {
@@ -83,11 +85,11 @@ label[for="kiwix_button_show_toggle"],
.kiwix #kiwixtoolbar button,
.kiwix #kiwixtoolbar input[type="submit"] {
box-sizing: border-box !important;
height: 26px !important;
height: 30px !important;
line-height: 20px !important;
margin-right: 5px !important;
padding: 2px 6px !important;
border: 1px solid #999 !important;
border: 1px solid #b5b2b2 !important;
border-radius: 3px !important;
background-color: #ededed !important;
font-weight: normal !important;
@@ -99,9 +101,9 @@ label[for="kiwix_button_show_toggle"],
left: 0;
box-sizing: border-box !important;
width: 100%;
height: 26px !important;
height: 30px !important;
line-height: 20px !important;
border: 1px solid #999 !important;
border: 1px solid #b5b2b2 !important;
border-radius: 3px !important;
background-color: #fff !important;
padding: 2px 2px 2px 27px !important;
@@ -114,11 +116,12 @@ label[for=kiwixsearchbox] {
height: 100%;
left: 5px;
font-size: 90%;
line-height: 26px;
line-height: 30px;
vertical-align: middle;
}
a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
font-size: 1.6rem;
display: block;
text-decoration: none;
color: inherit;
@@ -129,81 +132,6 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
column-count: 1 !important;
}
.modal-wrapper {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
background-color: rgba(0, 0, 0, 30%);
}
.modal {
color: #444343;
height: 280px;
width: 250px;
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
}
.modal-heading {
background-color: #f0f0f0;
height: 20%;
width: 100%;
border-bottom: 1px solid #ececec;
display: grid;
grid-template-columns: 3fr 1fr;
}
.modal-title {
display: flex;
font-size: 15px;
align-items: center;
padding-left: 20px;
font-family: poppins;
}
.modal-close-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
padding: 20px;
}
#uiLanguageSelector {
display: none;
}
#uiLanguageSelector .modal {
height: 140px;
}
#uiLanguageSelector .modal-heading {
height: 40%;
}
#uiLanguageSelector .modal-content #ui_language {
width: 100%;
}
#uiLanguageSelectorButton {
margin: 0px 12px 6px 12px;
float: right;
cursor: pointer;
height: 30px;
}
@media(min-width:420px) {
.kiwix_button_cont {
display: inline-block !important;
@@ -250,8 +178,12 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
}
}
@media(max-width:415px) {
@media(max-width:419px) {
.kiwix_searchform {
width: 80%;
}
.kiwix_button_cont {
padding-top: 5px;
}
}

View File

@@ -54,6 +54,22 @@ function gotoRandomPage() {
gotoUrl(`/random?content=${currentBook}`);
}
// URI-encodes only the specified special symbols (note, however, that '%' is
// always considered a special symbol).
function quasiUriEncode(s, specialSymbols) {
if ( specialSymbols.match(/[A-Za-z0-9]/) ) {
throw "Alphanumeric symbols cannot be special";
}
// Set's guarantee of iterating in insertion order ensures that
// all %s in s will be encoded first.
for ( const c of new Set('%' + specialSymbols) ) {
s = s.replaceAll(c, encodeURIComponent(c));
}
return s;
}
function performSearch() {
const searchbox = document.getElementById('kiwixsearchbox');
const q = encodeURIComponent(searchbox.value);
@@ -204,14 +220,30 @@ function handle_visual_viewport_change() {
contentIframe.height = wh - contentIframe.offsetTop - 4;
}
function setIframeUrl(path) {
try {
// Don't do anything if we are already at the requested URL.
// This is needed to break the infinite ping-pong played by
// handle_location_hash_change() and handle_content_url_change()
// (when either top-window or content window/iframe URL changes the other
// one is updated too).
if ( path == contentIframe.contentWindow.location.pathname )
return;
} catch ( error ) {
// This happens in Firefox when a PDF file is displayed in the iframe
// (sandboxing of the iframe content and cross-origin mismatch with the
// builtin PDF viewer result in preventing access to the attributes of
// contentIframe.contentWindow.location).
// Fall through and load the requested URL.
}
contentIframe.contentWindow.location.replace(path);
}
function handle_location_hash_change() {
const hash = window.location.hash.slice(1);
console.log("handle_location_hash_change: " + hash);
updateCurrentBookIfNeeded(hash);
const iframeContentUrl = userUrl2IframeUrl(hash);
if ( iframeContentUrl != contentIframe.contentWindow.location.pathname ) {
contentIframe.contentWindow.location.replace(iframeContentUrl);
}
setIframeUrl(userUrl2IframeUrl(hash));
updateSearchBoxForLocationChange();
previousScrollTop = Infinity;
history.replaceState(viewerState, null);
@@ -243,9 +275,10 @@ function matchingAncestorElement(el, context, selector) {
const block_path = `${root}/catch/external`;
function blockLink(target) {
const encodedHref = encodeURIComponent(target.href);
target.setAttribute("href", block_path + "?source=" + encodedHref);
function blockLink(url) {
return viewerSettings.linkBlockingEnabled
? block_path + "?source=" + encodeURIComponent(url)
: url;
}
function isExternalUrl(url) {
@@ -257,17 +290,20 @@ function isExternalUrl(url) {
|| url.startsWith("https:");
}
function handleLinkClick(e) {
function onClickEvent(e) {
const iframeDocument = contentIframe.contentDocument;
const target = matchingAncestorElement(e.target, iframeDocument, "a");
if (target !== null && "href" in target) {
if ( isExternalUrl(target.href) ) {
target.setAttribute("target", "_top");
if ( viewerSettings.linkBlockingEnabled ) {
return blockLink(target);
const possiblyBlockedLink = blockLink(target.href);
if ( e.ctrlKey || e.shiftKey ) {
// The link will be loaded in a new tab/window - update the link
// and let the browser handle the rest.
target.setAttribute("href", possiblyBlockedLink);
} else {
// Load the external URL in the viewer window (rather than iframe)
contentIframe.contentWindow.parent.location = possiblyBlockedLink;
}
} else {
target.setAttribute("target", "content_iframe");
}
}
}
@@ -303,7 +339,7 @@ this.Element && function(ElementPrototype) {
}(Element.prototype);
function setup_external_link_blocker() {
setupEventHandler(contentIframe.contentDocument, 'a', 'click', handleLinkClick);
setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent);
}
////////////////////////////////////////////////////////////////////////////////
@@ -315,8 +351,8 @@ let viewerSetupComplete = false;
function on_content_load() {
if ( viewerSetupComplete ) {
handle_content_url_change();
setup_external_link_blocker();
}
setup_external_link_blocker();
}
function htmlDecode(input) {
@@ -366,8 +402,13 @@ function setupSuggestions() {
const uriEncodedBookName = encodeURIComponent(currentBook);
let url;
if (data.value.kind == "path") {
const path = encodeURIComponent(htmlDecode(data.value.path));
url = `/content/${uriEncodedBookName}/${path}`;
// The double quote and backslash symbols are included in the list
// of special symbols to URI-encode so that the resulting URL can
// be safely quoted inside a dynamically executed piece of
// Javascript code a few lines later.
const path = htmlDecode(data.value.path);
const quasiUriEncodedPath = quasiUriEncode(path, '#?"\\');
url = `/content/${uriEncodedBookName}/${quasiUriEncodedPath}`;
} else {
const pattern = encodeURIComponent(htmlDecode(data.value.value));
url = `/search?content=${uriEncodedBookName}&pattern=${pattern}`;
@@ -508,7 +549,3 @@ function finishViewerSetupOnceTranslationsAreLoaded()
viewerSetupComplete = true;
}
function setPermanentGlobalCookie(name, value) {
document.cookie = `${name}=${value};path=${root};max-age=31536000`;
}

View File

@@ -5,6 +5,11 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link type="root" href="{{root}}">
<title>Welcome to Kiwix Server</title>
<link
type="text/css"
href="{{root}}/skin/kiwix.css?KIWIXCACHEID"
rel="Stylesheet"
/>
<link
type="text/css"
href="{{root}}/skin/index.css?KIWIXCACHEID"
@@ -26,17 +31,7 @@
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="{{root}}/skin/favicon/browserconfig.xml?KIWIXCACHEID">
<meta name="theme-color" content="#ffffff">
<style>
@font-face {
font-family: "poppins";
src: url("{{root}}/skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");
}
@font-face {
font-family: "roboto";
src: url("{{root}}/skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype");
}
</style>
<script type="text/javascript" src="./viewer_settings.js"></script>
<script type="module" src="{{root}}/skin/i18n.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="{{root}}/skin/languages.js?KIWIXCACHEID" defer></script>
<script src="{{root}}/skin/isotope.pkgd.min.js?KIWIXCACHEID" defer></script>

View File

@@ -5,6 +5,11 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link type="root" href="{{root}}">
<title>{{translations.welcome-to-kiwix-server}}</title>
<link
type="text/css"
href="{{root}}/skin/kiwix.css?KIWIXCACHEID"
rel="Stylesheet"
/>
<link
type="text/css"
href="{{root}}/skin/index.css?KIWIXCACHEID"
@@ -137,4 +142,4 @@
</div>
<div id="kiwixfooter" class="kiwixfooter">{{{translations.powered-by-kiwix-html}}}</div>
</body>
</html>
</html>

View File

@@ -1,5 +1,6 @@
const viewerSettings = {
toolbarEnabled: {{enable_toolbar}},
linkBlockingEnabled: {{enable_link_blocking}},
libraryButtonEnabled: {{enable_library_button}}
libraryButtonEnabled: {{enable_library_button}},
defaultUserLanguage: "{{default_user_language}}"
}

View File

@@ -8,13 +8,14 @@
object-src 'none';">
<title>ZIM Viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link type="text/css" href="./skin/kiwix.css?KIWIXCACHEID" rel="Stylesheet" />
<link type="text/css" href="./skin/taskbar.css?KIWIXCACHEID" rel="Stylesheet" />
<link type="text/css" href="./skin/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
<script type="text/javascript" src="./viewer_settings.js"></script>
<script type="module" src="./skin/i18n.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="./skin/languages.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="./skin/viewer.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?KIWIXCACHEID"></script>
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?KIWIXCACHEID"></script>
<script>
function getRootLocation() {
const p = location.pathname;
@@ -34,13 +35,6 @@
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
<div id="kiwixtoolbar" class="ui-widget-header">
<div class="kiwix_centered">
<a id="uiLanguageSelectorButton"
onclick="window.modalUILanguageSelector.show()"
alt="Select UI language"
aria-label="Select UI language"
title="Select UI language">
<img src="./skin/langSelector.svg?KIWIXCACHEID">
</a>
<div class="kiwix_searchform">
<form class="kiwixsearch" method="GET" action="javascript:performSearch()" id="kiwixsearchform">
<label for="kiwixsearchbox">&#x1f50d;</label>
@@ -65,11 +59,17 @@
</span>
</div>
</div>
<a onclick="window.modalUILanguageSelector.show()"
alt="Select UI language"
aria-label="Select UI language"
title="Select UI language">
<img id="uiLanguageSelectorButton"
src="./skin/langSelector.svg?KIWIXCACHEID">
</a>
</div>
</div>
<iframe id="content_iframe"
name="content_iframe"
referrerpolicy="no-referrer"
onload="on_content_load()"
src="./skin/blank.html?KIWIXCACHEID" title="ZIM content" width="100%"

View File

@@ -32,6 +32,7 @@
articleCount="284"
mediaCount="2"
size="123"
faviconMimeType="image/png;favicon_attribute_is_absent"
></book>
<book
id="charlesray"
@@ -48,5 +49,6 @@
articleCount="284"
mediaCount="2"
size="556"
favicon="faviconMimeType_attribute_is_absent"
></book>
</library>

41
test/languageTools.cpp Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Nikhil Tanwar (2002nikhiltanwar@gmail.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
* NON-INFRINGEMENT. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gtest/gtest.h"
#include "../include/tools.h"
namespace
{
TEST(LanguageToolsTest, englishTest)
{
EXPECT_EQ(kiwix::getLanguageSelfName("eng"), "English");
}
TEST(LanguageToolsTest, manualValuesTest)
{
EXPECT_EQ(kiwix::getLanguageSelfName("dty"), "डोटेली");
}
TEST(LanguageToolsTest, emptyStringTest)
{
EXPECT_EQ(kiwix::getLanguageSelfName(""), "Undetermined");
}
}

View File

@@ -218,7 +218,7 @@ const char sampleLibraryXML[] = R"(
creator="Wikibooks"
publisher="Kiwix & Some Enthusiasts"
date="2021-04-11"
name="wikibooks_de"
name="wikibooks.de"
tags="unittest;wikibooks;_category:wikibooks"
articleCount="12"
mediaCount="0"
@@ -238,14 +238,14 @@ typedef std::vector<std::string> Langs;
TEST(LibraryOpdsImportTest, allInOne)
{
kiwix::Library lib;
kiwix::Manager manager(&lib);
auto lib = kiwix::Library::create();
kiwix::Manager manager(lib);
manager.readOpds(sampleOpdsStream, "library-opds-import.unittests.dev");
EXPECT_EQ(10U, lib.getBookCount(true, true));
EXPECT_EQ(10U, lib->getBookCount(true, true));
{
const kiwix::Book& book1 = lib.getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd");
const kiwix::Book& book1 = lib->getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd");
EXPECT_EQ(book1.getTitle(), "Encyclopédie de la Tunisie");
EXPECT_EQ(book1.getName(), "wikipedia_fr_tunisie_novid_2018-10");
@@ -271,7 +271,7 @@ TEST(LibraryOpdsImportTest, allInOne)
}
{
const kiwix::Book& book2 = lib.getBookById("0189d9be-2fd0-b4b6-7300-20fab0b5cdc8");
const kiwix::Book& book2 = lib->getBookById("0189d9be-2fd0-b4b6-7300-20fab0b5cdc8");
EXPECT_EQ(book2.getTitle(), "TED talks - Business");
EXPECT_EQ(book2.getName(), "");
EXPECT_EQ(book2.getFlavour(), "");
@@ -301,8 +301,10 @@ class LibraryTest : public ::testing::Test {
typedef kiwix::Library::BookIdCollection BookIdCollection;
typedef std::vector<std::string> TitleCollection;
LibraryTest(): lib(kiwix::Library::create()) {}
void SetUp() override {
kiwix::Manager manager(&lib);
kiwix::Manager manager(lib);
manager.readOpds(sampleOpdsStream, "foo.urlHost");
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
}
@@ -316,25 +318,25 @@ class LibraryTest : public ::testing::Test {
TitleCollection ids2Titles(const BookIdCollection& ids) {
TitleCollection titles;
for ( const auto& bookId : ids ) {
titles.push_back(lib.getBookById(bookId).getTitle());
titles.push_back(lib->getBookById(bookId).getTitle());
}
std::sort(titles.begin(), titles.end());
return titles;
}
kiwix::Library lib;
std::shared_ptr<kiwix::Library> lib;
};
TEST_F(LibraryTest, getBookMarksTest)
{
auto bookId1 = lib.getBooksIds()[0];
auto bookId2 = lib.getBooksIds()[1];
auto bookId1 = lib->getBooksIds()[0];
auto bookId2 = lib->getBooksIds()[1];
lib.addBookmark(createBookmark(bookId1));
lib.addBookmark(createBookmark("invalid-bookmark-id"));
lib.addBookmark(createBookmark(bookId2));
auto onlyValidBookmarks = lib.getBookmarks();
auto allBookmarks = lib.getBookmarks(false);
lib->addBookmark(createBookmark(bookId1));
lib->addBookmark(createBookmark("invalid-bookmark-id"));
lib->addBookmark(createBookmark(bookId2));
auto onlyValidBookmarks = lib->getBookmarks();
auto allBookmarks = lib->getBookmarks(false);
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
@@ -346,11 +348,11 @@ TEST_F(LibraryTest, getBookMarksTest)
TEST_F(LibraryTest, sanityCheck)
{
EXPECT_EQ(lib.getBookCount(true, true), 12U);
EXPECT_EQ(lib.getBooksLanguages(),
EXPECT_EQ(lib->getBookCount(true, true), 12U);
EXPECT_EQ(lib->getBooksLanguages(),
std::vector<std::string>({"deu", "eng", "fra", "ita", "spa"})
);
EXPECT_EQ(lib.getBooksCreators(), std::vector<std::string>({
EXPECT_EQ(lib->getBooksCreators(), std::vector<std::string>({
"Islam Stack Exchange",
"Movies & TV Stack Exchange",
"Mythology & Folklore Stack Exchange",
@@ -361,7 +363,7 @@ TEST_F(LibraryTest, sanityCheck)
"Wikipedia",
"Wikiquote"
}));
EXPECT_EQ(lib.getBooksPublishers(), std::vector<std::string>({
EXPECT_EQ(lib->getBooksPublishers(), std::vector<std::string>({
"",
"Kiwix",
"Kiwix & Some Enthusiasts",
@@ -371,22 +373,22 @@ TEST_F(LibraryTest, sanityCheck)
TEST_F(LibraryTest, categoryHandling)
{
EXPECT_EQ("", lib.getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd").getCategory());
EXPECT_EQ("category_defined_via_tags_only", lib.getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2").getCategory());
EXPECT_EQ("category_defined_via_category_element_only", lib.getBookById("0ea1cde6-441d-6c58-f2c7-21c2838e659f").getCategory());
EXPECT_EQ("category_element_overrides_tags", lib.getBookById("1123e574-6eef-6d54-28fc-13e4caeae474").getCategory());
EXPECT_EQ("category_element_overrides_tags", lib.getBookById("14829621-c490-c376-0792-9de558b57efa").getCategory());
EXPECT_EQ("", lib->getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd").getCategory());
EXPECT_EQ("category_defined_via_tags_only", lib->getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2").getCategory());
EXPECT_EQ("category_defined_via_category_element_only", lib->getBookById("0ea1cde6-441d-6c58-f2c7-21c2838e659f").getCategory());
EXPECT_EQ("category_element_overrides_tags", lib->getBookById("1123e574-6eef-6d54-28fc-13e4caeae474").getCategory());
EXPECT_EQ("category_element_overrides_tags", lib->getBookById("14829621-c490-c376-0792-9de558b57efa").getCategory());
}
TEST_F(LibraryTest, emptyFilter)
{
const auto bookIds = lib.filter(kiwix::Filter());
EXPECT_EQ(bookIds, lib.getBooksIds());
const auto bookIds = lib->filter(kiwix::Filter());
EXPECT_EQ(bookIds, lib->getBooksIds());
}
#define EXPECT_FILTER_RESULTS(f, ...) \
EXPECT_EQ( \
ids2Titles(lib.filter(f)), \
ids2Titles(lib->filter(f)), \
TitleCollection({ __VA_ARGS__ }) \
)
@@ -678,17 +680,38 @@ TEST_F(LibraryTest, filterByPublisher)
TEST_F(LibraryTest, filterByName)
{
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikibooks_de"),
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikibooks.de"),
"An example ZIM archive"
);
EXPECT_FILTER_RESULTS(kiwix::Filter().query("name:wikibooks_de"),
"An example ZIM archive"
);
EXPECT_FILTER_RESULTS(kiwix::Filter().query("wikibooks_de"),
// Parsing the query with `name:` prefix splits the token on the dot, as if it was 2 sentences.
// It creates a query "XNwikibook@1 PHRASE 2 XNde@2".
// I haven't found the syntax to not split on dot.
EXPECT_FILTER_RESULTS(kiwix::Filter().query("name:wikibooks.de"),
/* no results */
);
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikibooks"),
/* no results */
);
// Wikibooks is in `tags` so it matches.
EXPECT_FILTER_RESULTS(kiwix::Filter().query("wikibooks"),
"An example ZIM archive"
);
// But "wikibooks.de" is only in name and `query` doesn't looks in name.
EXPECT_FILTER_RESULTS(kiwix::Filter().query("wikibooks.de"),
/* no results */
);
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikipedia_en_ray_charles"),
"Ray Charles"
);
EXPECT_FILTER_RESULTS(kiwix::Filter().query("name:wikipedia_en_ray_charles"),
"Ray Charles"
);
}
TEST_F(LibraryTest, filterByCategory)
@@ -736,33 +759,33 @@ TEST_F(LibraryTest, filterByMultipleCriteria)
TEST_F(LibraryTest, getBookByPath)
{
kiwix::Book book = lib.getBookById(lib.getBooksIds()[0]);
kiwix::Book book = lib->getBookById(lib->getBooksIds()[0]);
#ifdef _WIN32
auto path = "C:\\some\\abs\\path.zim";
#else
auto path = "/some/abs/path.zim";
#endif
book.setPath(path);
lib.addBook(book);
EXPECT_EQ(lib.getBookByPath(path).getId(), book.getId());
EXPECT_THROW(lib.getBookByPath("non/existant/path.zim"), std::out_of_range);
lib->addBook(book);
EXPECT_EQ(lib->getBookByPath(path).getId(), book.getId());
EXPECT_THROW(lib->getBookByPath("non/existant/path.zim"), std::out_of_range);
}
TEST_F(LibraryTest, removeBookByIdRemovesTheBook)
{
const auto initialBookCount = lib.getBookCount(true, true);
const auto initialBookCount = lib->getBookCount(true, true);
ASSERT_GT(initialBookCount, 0U);
EXPECT_NO_THROW(lib.getBookById("raycharles"));
lib.removeBookById("raycharles");
EXPECT_EQ(initialBookCount - 1, lib.getBookCount(true, true));
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
EXPECT_NO_THROW(lib->getBookById("raycharles"));
lib->removeBookById("raycharles");
EXPECT_EQ(initialBookCount - 1, lib->getBookCount(true, true));
EXPECT_THROW(lib->getBookById("raycharles"), std::out_of_range);
};
TEST_F(LibraryTest, removeBookByIdDropsTheReader)
{
EXPECT_NE(nullptr, lib.getArchiveById("raycharles"));
lib.removeBookById("raycharles");
EXPECT_THROW(lib.getArchiveById("raycharles"), std::out_of_range);
EXPECT_NE(nullptr, lib->getArchiveById("raycharles"));
lib->removeBookById("raycharles");
EXPECT_THROW(lib->getArchiveById("raycharles"), std::out_of_range);
};
TEST_F(LibraryTest, removeBookByIdUpdatesTheSearchDB)
@@ -770,17 +793,17 @@ TEST_F(LibraryTest, removeBookByIdUpdatesTheSearchDB)
kiwix::Filter f;
f.local(true).valid(true).query(R"(title:"ray charles")", false);
EXPECT_NO_THROW(lib.getBookById("raycharles"));
EXPECT_EQ(1U, lib.filter(f).size());
EXPECT_NO_THROW(lib->getBookById("raycharles"));
EXPECT_EQ(1U, lib->filter(f).size());
lib.removeBookById("raycharles");
lib->removeBookById("raycharles");
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
EXPECT_EQ(0U, lib.filter(f).size());
EXPECT_THROW(lib->getBookById("raycharles"), std::out_of_range);
EXPECT_EQ(0U, lib->filter(f).size());
// make sure that Library::filter() doesn't add an empty book with
// an id surviving in the search DB
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
EXPECT_THROW(lib->getBookById("raycharles"), std::out_of_range);
};
TEST_F(LibraryTest, removeBooksNotUpdatedSince)
@@ -800,18 +823,18 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
"Wikiquote"
);
const uint64_t rev = lib.getRevision();
for ( const auto& id : lib.filter(kiwix::Filter().query("exchange")) ) {
lib.addBook(lib.getBookByIdThreadSafe(id));
const uint64_t rev = lib->getRevision();
for ( const auto& id : lib->filter(kiwix::Filter().query("exchange")) ) {
lib->addBook(lib->getBookByIdThreadSafe(id));
}
EXPECT_GT(lib.getRevision(), rev);
EXPECT_GT(lib->getRevision(), rev);
const uint64_t rev2 = lib.getRevision();
const uint64_t rev2 = lib->getRevision();
EXPECT_EQ(9u, lib.removeBooksNotUpdatedSince(rev));
EXPECT_EQ(9u, lib->removeBooksNotUpdatedSince(rev));
EXPECT_GT(lib.getRevision(), rev2);
EXPECT_GT(lib->getRevision(), rev2);
EXPECT_FILTER_RESULTS(kiwix::Filter(),
"Islam Stack Exchange",

View File

@@ -301,20 +301,41 @@ TEST_F(LibraryServerTest, catalog_search_by_tag)
TEST_F(LibraryServerTest, catalog_search_by_category)
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/search?category=jazz");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (category=jazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>1</itemsPerPage>\n"
CATALOG_LINK_TAGS
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/search?category=jazz");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (category=jazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>1</itemsPerPage>\n"
CATALOG_LINK_TAGS
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/search?category=jazz,wikipedia");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (category=jazz%2Cwikipedia)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>2</itemsPerPage>\n"
CATALOG_LINK_TAGS
RAY_CHARLES_CATALOG_ENTRY
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
}
TEST_F(LibraryServerTest, catalog_search_by_language)
@@ -793,6 +814,40 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_language)
}
}
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_category)
{
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries?category=jazz");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?category=jazz")
" <title>Filtered Entries (category=jazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>1</itemsPerPage>\n"
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries?category=jazz,wikipedia");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?category=jazz%2Cwikipedia")
" <title>Filtered Entries (category=jazz%2Cwikipedia)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>2</itemsPerPage>\n"
RAY_CHARLES_CATALOG_ENTRY
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
}
TEST_F(LibraryServerTest, catalog_v2_entries_multiple_filters)
{
{
@@ -973,7 +1028,12 @@ 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/index.css?cacheid=e4d76d16\"\n" \
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link\n" \
" type=\"text/css\"\n" \
" href=\"/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3\">\n" \
@@ -1090,7 +1150,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" </div>\n" \
" <div id=\"kiwixfooter\" class=\"kiwixfooter\">Powered by&nbsp;<a href=\"https://kiwix.org\">Kiwix</a></div>\n" \
" </body>\n" \
"</html>"
"</html>\n"
#define FILTERS_HTML(SELECTED_ENG) \
" <div class=\"kiwixNav__filters\">\n" \

View File

@@ -8,18 +8,18 @@
TEST(ManagerTest, addBookFromPathAndGetIdTest)
{
kiwix::Library lib;
kiwix::Manager manager = kiwix::Manager(&lib);
auto lib = kiwix::Library::create();
kiwix::Manager manager = kiwix::Manager(lib);
auto bookId = manager.addBookFromPathAndGetId("./test/example.zim");
ASSERT_NE(bookId, "");
kiwix::Book book = lib.getBookById(bookId);
kiwix::Book book = lib->getBookById(bookId);
EXPECT_EQ(book.getPath(), kiwix::computeAbsolutePath("", "./test/example.zim"));
const std::string pathToSave = "./pathToSave";
const std::string url = "url";
bookId = manager.addBookFromPathAndGetId("./test/example.zim", pathToSave, url, true);
book = lib.getBookById(bookId);
book = lib->getBookById(bookId);
auto savedPath = kiwix::computeAbsolutePath(kiwix::removeLastPathElement(manager.writableLibraryPath), pathToSave);
EXPECT_EQ(book.getPath(), savedPath);
EXPECT_EQ(book.getUrl(), url);
@@ -48,11 +48,11 @@ const char sampleLibraryXML[] = R"(
TEST(ManagerTest, readXml)
{
kiwix::Library lib;
kiwix::Manager manager = kiwix::Manager(&lib);
auto lib = kiwix::Library::create();
kiwix::Manager manager = kiwix::Manager(lib);
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, "/data/lib.xml", true));
kiwix::Book book = lib.getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
kiwix::Book book = lib->getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
EXPECT_EQ("/data/zimfiles/unittest.zim", book.getPath());
EXPECT_EQ("https://example.com/zimfiles/unittest.zim", book.getUrl());
EXPECT_EQ("Unit Test", book.getTitle());
@@ -70,24 +70,24 @@ TEST(ManagerTest, readXml)
TEST(Manager, reload)
{
kiwix::Library lib;
kiwix::Manager manager(&lib);
auto lib = kiwix::Library::create();
kiwix::Manager manager(lib);
manager.reload({ "./test/library.xml" });
EXPECT_EQ(lib.getBooksIds(), (kiwix::Library::BookIdCollection{
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
"charlesray",
"raycharles",
"raycharles_uncategorized"
}));
lib.removeBookById("raycharles");
EXPECT_EQ(lib.getBooksIds(), (kiwix::Library::BookIdCollection{
lib->removeBookById("raycharles");
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
"charlesray",
"raycharles_uncategorized"
}));
manager.reload({ "./test/library.xml" });
EXPECT_EQ(lib.getBooksIds(), kiwix::Library::BookIdCollection({
EXPECT_EQ(lib->getBooksIds(), kiwix::Library::BookIdCollection({
"charlesray",
"raycharles",
"raycharles_uncategorized"

View File

@@ -5,6 +5,8 @@ tests = [
'stringTools',
'pathTools',
'otherTools',
'opdsParsingTools',
'languageTools',
'kiwixserve',
'book',
'manager',

View File

@@ -18,18 +18,20 @@ const char libraryXML[] = R"(
)";
class NameMapperTest : public ::testing::Test {
public:
NameMapperTest(): lib(kiwix::Library::create()) {}
protected:
void SetUp() override {
kiwix::Manager manager(&lib);
kiwix::Manager manager(lib);
manager.readXml(libraryXML, false, "./library.xml", true);
for ( const std::string& id : lib.getBooksIds() ) {
kiwix::Book bookCopy = lib.getBookById(id);
for ( const std::string& id : lib->getBooksIds() ) {
kiwix::Book bookCopy = lib->getBookById(id);
bookCopy.setPathValid(true);
lib.addBook(bookCopy);
lib->addBook(bookCopy);
}
}
kiwix::Library lib;
std::shared_ptr<kiwix::Library> lib;
};
class CapturedStderr
@@ -73,13 +75,13 @@ void checkUnaliasedEntriesInNameMapper(const kiwix::NameMapper& nm)
TEST_F(NameMapperTest, HumanReadableNameMapperWithoutAliases)
{
CapturedStderr stderror;
kiwix::HumanReadableNameMapper nm(lib, false);
kiwix::HumanReadableNameMapper nm(*lib, false);
EXPECT_EQ("", std::string(stderror));
checkUnaliasedEntriesInNameMapper(nm);
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
lib.removeBookById("04-2021-10");
lib->removeBookById("04-2021-10");
EXPECT_EQ("zero_four_2021-10", nm.getNameForId("04-2021-10"));
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four_2021-10"));
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
@@ -88,7 +90,7 @@ TEST_F(NameMapperTest, HumanReadableNameMapperWithoutAliases)
TEST_F(NameMapperTest, HumanReadableNameMapperWithAliases)
{
CapturedStderr stderror;
kiwix::HumanReadableNameMapper nm(lib, true);
kiwix::HumanReadableNameMapper nm(*lib, true);
EXPECT_EQ(
"Path collision: /data/zero_four_2021-10.zim and"
" /data/zero_four_2021-11.zim can't share the same URL path 'zero_four'."
@@ -99,7 +101,7 @@ TEST_F(NameMapperTest, HumanReadableNameMapperWithAliases)
checkUnaliasedEntriesInNameMapper(nm);
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four"));
lib.removeBookById("04-2021-10");
lib->removeBookById("04-2021-10");
EXPECT_EQ("zero_four_2021-10", nm.getNameForId("04-2021-10"));
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four_2021-10"));
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four"));
@@ -114,7 +116,7 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithoutAliases)
checkUnaliasedEntriesInNameMapper(nm);
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
lib.removeBookById("04-2021-10");
lib->removeBookById("04-2021-10");
nm.update();
EXPECT_THROW(nm.getNameForId("04-2021-10"), std::out_of_range);
EXPECT_THROW(nm.getIdForName("zero_four_2021-10"), std::out_of_range);
@@ -137,7 +139,7 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithAliases)
{
CapturedStderr nmUpdateStderror;
lib.removeBookById("04-2021-10");
lib->removeBookById("04-2021-10");
nm.update();
EXPECT_EQ("", std::string(nmUpdateStderror));
}

131
test/opdsParsingTools.cpp Normal file
View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) 2023 Nikhil Tanwar (2002nikhiltanwar@gmail.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
* NON-INFRINGEMENT. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gtest/gtest.h"
#include "../include/tools.h"
typedef kiwix::FeedLanguages FeedLanguages;
typedef kiwix::FeedCategories FeedCategories;
namespace
{
const char sampleLanguageOpdsStream[] = R"(
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:opds="https://specs.opds.io/opds-1.2"
xmlns:thr="http://purl.org/syndication/thread/1.0">
<id>1e587935-0f7b-dad6-eddc-ef3fafd4c3ed</id>
<link rel="self"
href="/catalog/v2/languages"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="start"
href="/catalog/v2/root.xml"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<title>List of languages</title>
<updated>2023-07-11T15:35:09Z</updated>
<entry>
<title>Abkhazian</title>
<dc:language>abk</dc:language>
<thr:count>3</thr:count>
<link rel="subsection"
href="/catalog/v2/entries?lang=abk"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>2e4d9a1c-9750-0418-8124-a0c663e206f7</id>
</entry>
<entry>
<title>isiZulu</title>
<dc:language>zul</dc:language>
<thr:count>4</thr:count>
<link rel="subsection"
href="/catalog/v2/entries?lang=zul"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>76eec223-994d-9b95-e309-baee06e585b0</id>
</entry>
</feed>
)";
const char sampleCategoriesOpdsStream[] = R"(
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:opds="https://specs.opds.io/opds-1.2">
<id>231da20c-0fe0-7345-11b2-d29f50364108</id>
<link rel="self"
href="/catalog/v2/categories"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="start"
href="/catalog/v2/root.xml"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<title>List of categories</title>
<updated>2023-07-11T15:35:09Z</updated>
<entry>
<title>gutenberg</title>
<link rel="subsection"
href="/catalog/v2/entries?category=gutenberg"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>401dbe68-2f7a-5503-b431-054801c30bab</id>
<content type="text">All entries with category of 'gutenberg'.</content>
</entry>
<entry>
<title>iFixit</title>
<link rel="subsection"
href="/catalog/v2/entries?category=iFixit"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>c18e5459-af23-5fbf-0622-ff271bd9a5ad</id>
<content type="text">All entries with category of 'iFixit'.</content>
</entry>
<entry>
<title>wikivoyage</title>
<link rel="subsection"
href="/catalog/v2/entries?category=wikivoyage"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>9a75be6c-7a35-6f52-1a69-bee9ad248459</id>
<content type="text">All entries with category of 'wikivoyage'.</content>
</entry>
<entry>
<title>wiktionary</title>
<link rel="subsection"
href="/catalog/v2/entries?category=wiktionary"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>7adb9f1a-73d7-0391-1238-d2e2c300ddaa</id>
<content type="text">All entries with category of 'wiktionary'.</content>
</entry>
</feed>
)";
TEST(OpdsParsingTest, languageTest)
{
FeedLanguages expectedLanguagesFromFeed = {{"abk", "Abkhazian"}, {"zul", "isiZulu"}};
EXPECT_EQ(kiwix::readLanguagesFromFeed(sampleLanguageOpdsStream), expectedLanguagesFromFeed);
}
TEST(OpdsParsingTest, categoryTest)
{
FeedCategories expectedCategoriesFromFeed = {"gutenberg", "iFixit", "wikivoyage", "wiktionary"};
EXPECT_EQ(kiwix::readCategoriesFromFeed(sampleCategoriesOpdsStream), expectedCategoriesFromFeed);
}
}

View File

@@ -100,7 +100,7 @@ TEST(Suggestions, specialCharHandling)
{
// HTML special symbols (<, >, &, ", and ') must be HTML-escaped
// Backslash symbols (\) must be duplicated.
const std::string SYMBOLS(R"(\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?)");
const std::string SYMBOLS("\t\n\r" R"(\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?)");
{
kiwix::Suggestions s;
s.add(zim::SuggestionItem("Title with " + SYMBOLS,
@@ -110,10 +110,10 @@ TEST(Suggestions, specialCharHandling)
CHECK_SUGGESTIONS(s.getJSON(),
R"EXPECTEDJSON([
{
"value" : "Title with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippet with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"value" : "Title with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippet with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"kind" : "path"
, "path" : "Path with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
, "path" : "Path with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
}
]
)EXPECTEDJSON"
@@ -128,10 +128,10 @@ R"EXPECTEDJSON([
CHECK_SUGGESTIONS(s.getJSON(),
R"EXPECTEDJSON([
{
"value" : "Snippetless title with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippetless title with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"value" : "Snippetless title with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippetless title with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"kind" : "path"
, "path" : "Path with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
, "path" : "Path with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
}
]
)EXPECTEDJSON"
@@ -145,8 +145,8 @@ R"EXPECTEDJSON([
CHECK_SUGGESTIONS(s.getJSON(),
R"EXPECTEDJSON([
{
"value" : "text with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.? ",
"label" : "containing &apos;text with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?&apos;...",
"value" : "text with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.? ",
"label" : "containing &apos;text with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?&apos;...",
"kind" : "pattern"
//EOLWHITESPACEMARKER
}

View File

@@ -54,26 +54,28 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/viewer" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/viewer?cacheid=whatever" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete.min.js?cacheid=1191aaaf" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/css/autoComplete.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/css/autoComplete.css?cacheid=08951e06" },
{ 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" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=2cf0f8c5" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=e4d76d16" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=07b06fca" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" },
{ 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" },
{ 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=bbdaf425" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=e014a885" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=b548ad94" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=201653b8" },
{ 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" },
@@ -147,7 +149,7 @@ const ResourceCollection resources200Uncompressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search_results.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/languages.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=b00b12db" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=96f2cf73" },
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Title" },
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Description" },
@@ -274,7 +276,8 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
const std::vector<UrlAndExpectedResult> testData{
{
/* url */ "/ROOT%23%3F/",
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16"
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
href="/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf"
<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">
<link rel="icon" type="image/png" sizes="16x16" href="/ROOT%23%3F/skin/favicon/favicon-16x16.png?cacheid=a986fedc">
@@ -282,15 +285,19 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16"
<link rel="mask-icon" href="/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95" color="#5bbad5">
<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">
src: url("/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype");
src: url("/ROOT%23%3F/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=b00b12db" defer></script>
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=96f2cf73" 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=07b06fca" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" defer></script>
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
)EXPECTEDRESULT"
},
{
/* url */ "/ROOT%23%3F/skin/kiwix.css",
R"EXPECTEDRESULT( src: url("../skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype");
src: url("../skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
)EXPECTEDRESULT"
},
{
@@ -308,15 +315,16 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download
},
{
/* url */ "/ROOT%23%3F/viewer",
R"EXPECTEDRESULT( <link type="text/css" href="./skin/taskbar.css?cacheid=bbdaf425" rel="Stylesheet" />
<link type="text/css" href="./skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
<script type="module" src="./skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=b00b12db" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=b548ad94" defer></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script>
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
<link type="text/css" href="./skin/taskbar.css?cacheid=e014a885" rel="Stylesheet" />
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
<script type="module" src="./skin/i18n.js?cacheid=6a8c6fb2" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=96f2cf73" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=201653b8" defer></script>
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<img src="./skin/langSelector.svg?cacheid=00b59961">
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
src="./skin/langSelector.svg?cacheid=00b59961">
src="./skin/blank.html?cacheid=6b1fa032" title="ZIM content" width="100%"
)EXPECTEDRESULT"
},
@@ -371,7 +379,7 @@ const char* urls404[] = {
"/ROOT%23%3",
"/ROOT%23%3Fxyz",
"/ROOT%23%3F/skin/non-existent-skin-resource",
"/ROOT%23%3F/skin/autoComplete.min.js?cacheid=wrongcacheid",
"/ROOT%23%3F/skin/autoComplete/autoComplete.min.js?cacheid=wrongcacheid",
"/ROOT%23%3F/catalog",
"/ROOT%23%3F/catalog/",
"/ROOT%23%3F/catalog/non-existent-item",
@@ -1057,14 +1065,26 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
"English": "en"
},
{
"français": "fr"
"español": "es"
},
{
"suomi": "fi"
},
{
"Français": "fr"
},
{
"עברית": "he"
},
{
"हिन्दी": "hi"
},
{
"Հայերեն": "hy"
},
{
"interlingua": "ia"
},
{
"italiano": "it"
},
@@ -1083,9 +1103,18 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"македонски": "mk"
},
{
"Bahasa Melayu": "ms"
},
{
"Nederlands": "nl"
},
{
"ߒߞߏ": "nqo"
},
{
"ଓଡ଼ିଆ": "or"
},
{
"Polski": "pl"
},
@@ -1098,12 +1127,21 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"slovenčina": "sk"
},
{
"سرائیکی": "skr-arab"
},
{
"slovenščina": "sl"
},
{
"Shqip": "sq"
},
{
"Svenska": "sv"
},
{
"ఇంగ్లీషు": "te"
},
{
"Türkçe": "tr"
},
@@ -1124,7 +1162,6 @@ TEST_F(ServerTest, UserLanguageControl)
const std::string url;
const std::string acceptLanguageHeader;
const char* const requestCookie; // Cookie: header of the request
const char* const responseSetCookie; // Set-Cookie: header of the response
const std::string expectedH1;
operator TestContext() const
@@ -1151,7 +1188,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@@ -1159,7 +1195,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@@ -1167,7 +1202,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
@@ -1175,7 +1209,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "*",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@@ -1183,71 +1216,20 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is respected",
"userlang cookie is ignored",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "anothercookie=123; userlang=test",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test; anothercookie=abc",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "cookie1=abc; userlang=test; cookie2=xyz",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"Multiple userlang cookies are not a problem",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "cookie1=abc; userlang=en; userlang=test; cookie2=xyz",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
/* expected <h1> */ "Not Found"
},
{
"userlang query parameter takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
"userlang query parameter takes precedence over its cookie counterpart",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test",
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
"userlang in cookies takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ "userlang=en",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "Not Found"
},
{
@@ -1257,7 +1239,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.9, en;q=0.2",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
@@ -1267,7 +1248,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.2, en;q=0.9",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
};
@@ -1283,12 +1263,7 @@ TEST_F(ServerTest, UserLanguageControl)
headers.insert({"Cookie", t.requestCookie});
}
const auto r = zfs1_->GET(t.url.c_str(), headers);
if ( t.responseSetCookie ) {
ASSERT_TRUE(r->has_header("Set-Cookie")) << t;
EXPECT_EQ(t.responseSetCookie, getHeaderValue(r->headers, "Set-Cookie")) << t;
} else {
EXPECT_FALSE(r->has_header("Set-Cookie"));
}
EXPECT_FALSE(r->has_header("Set-Cookie"));
std::regex_search(r->body, h1Match, h1Regex);
const std::string h1(h1Match[1]);
EXPECT_EQ(h1, t.expectedH1) << t;
@@ -1983,7 +1958,8 @@ TEST_F(ServerTest, viewerSettings)
R"(const viewerSettings = {
toolbarEnabled: false,
linkBlockingEnabled: false,
libraryButtonEnabled: false
libraryButtonEnabled: false,
defaultUserLanguage: "en"
}
)");
}
@@ -1994,7 +1970,8 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: false,
linkBlockingEnabled: true,
libraryButtonEnabled: false
libraryButtonEnabled: false,
defaultUserLanguage: "en"
}
)");
}
@@ -2005,7 +1982,8 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: false
libraryButtonEnabled: false,
defaultUserLanguage: "en"
}
)");
}
@@ -2016,7 +1994,47 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true
libraryButtonEnabled: true,
defaultUserLanguage: "en"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "fr"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "fr"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "test;q=0.2, en;q=0.9"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "en"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "test;q=0.9, en;q=0.2"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "test"
}
)");
}

View File

@@ -2,6 +2,7 @@
#define CPPHTTPLIB_ZLIB_SUPPORT 1
#include "./httplib.h"
#include "gtest/gtest.h"
#include "../src/tools/stringTools.h"
#define SERVER_PORT 8101
#include "server_testing_tools.h"
@@ -716,7 +717,7 @@ struct TestData
std::string getPattern() const
{
return extractQueryValue("pattern");
return kiwix::urlDecode(extractQueryValue("pattern"), true);
}
std::string getLang() const
@@ -1456,6 +1457,33 @@ TEST(ServerSearchTest, searchResults)
{ "", 40, false },
}
},
// Be sure that searching with accented query return the same things than non accented query.
{
/* query */ "pattern=j%C3%A0zz"
"&books.filter.lang=eng"
"&books.filter.title=Ray%20Charles",
/* start */ -1,
/* resultsPerPage */ 5,
/* totalResultCount */ 44,
/* firstResultIndex */ 1,
/* results */ {
LARGE_SEARCH_RESULTS[0],
LARGE_SEARCH_RESULTS[1],
LARGE_SEARCH_RESULTS[2],
LARGE_SEARCH_RESULTS[3],
LARGE_SEARCH_RESULTS[4],
},
/* pagination */ {
{ "1", 0, true },
{ "2", 5, false },
{ "3", 10, false },
{ "4", 15, false },
{ "5", 20, false },
{ "", 40, false },
}
},
};
ZimFileServer zfs(SERVER_PORT, ZimFileServer::DEFAULT_OPTIONS,

View File

@@ -98,16 +98,17 @@ private:
void run(int serverPort, std::string indexTemplateString = "");
private: // data
kiwix::Library library;
std::shared_ptr<kiwix::Library> library;
kiwix::Manager manager;
std::unique_ptr<kiwix::NameMapper> nameMapper;
std::shared_ptr<kiwix::NameMapper> nameMapper;
std::unique_ptr<kiwix::Server> server;
std::unique_ptr<httplib::Client> client;
const Cfg cfg;
};
ZimFileServer::ZimFileServer(int serverPort, Cfg _cfg, std::string libraryFilePath)
: manager(&this->library)
: library(kiwix::Library::create())
, manager(this->library)
, cfg(_cfg)
{
if ( kiwix::isRelativePath(libraryFilePath) )
@@ -120,7 +121,8 @@ ZimFileServer::ZimFileServer(int serverPort,
Cfg _cfg,
const FilePathCollection& zimpaths,
std::string indexTemplateString)
: manager(&this->library)
: library(kiwix::Library::create())
, manager(this->library)
, cfg(_cfg)
{
for ( const auto& zimpath : zimpaths ) {
@@ -136,9 +138,9 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
if (cfg.options & NO_NAME_MAPPER) {
nameMapper.reset(new kiwix::IdNameMapper());
} else {
nameMapper.reset(new kiwix::HumanReadableNameMapper(library, false));
nameMapper.reset(new kiwix::HumanReadableNameMapper(*library, false));
}
server.reset(new kiwix::Server(&library, nameMapper.get()));
server.reset(new kiwix::Server(library, nameMapper));
server->setRoot(cfg.root);
server->setAddress(address);
server->setPort(serverPort);