Compare commits

..

210 Commits

Author SHA1 Message Date
Veloman Yunkan
f8fc60ed2f Enabled smart mode of suggestions 2025-05-30 12:33:05 +04:00
Kelson
222e4396c7 Merge pull request #1195 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2025-05-19 14:26:47 +02:00
translatewiki.net
4c480952d1 Localisation updates from https://translatewiki.net. 2025-05-19 14:07:14 +02:00
Kelson
79479788f9 Merge pull request #1178 from kiwix/nicer_error_pages
Nicer 404 error and external link blocker pages
2025-05-17 13:56:46 +02:00
Veloman Yunkan
3cd1f7854a Fully translated external link blocker page 2025-05-14 21:40:23 +04:00
Veloman Yunkan
58a211d01d Renamed a parameter in external link blocker template 2025-05-14 21:37:52 +04:00
Veloman Yunkan
07fc40da5a Translation works on external link blocker
This comes at the cost of broken support for SeaMonkey (due to usage of
import.meta in i18n.js)
2025-05-14 21:36:43 +04:00
Veloman Yunkan
d961447e1e Started translating the external link blocker page
The external link blocker page isn't actually translated since it is not
managed by the viewer. Will port the translation code from the viewer.js
in next commit.
2025-05-14 21:35:29 +04:00
Veloman Yunkan
c9ebeb7b96 New external link blocker page
The page doesn't support translation yet.
2025-05-14 21:34:14 +04:00
Veloman Yunkan
2d73ed31a9 Handling translation in ServerTest.HttpSexy404HtmlError
The failing test point in the ServerTest.Http404HtmlError unit-test
has been superseded by the enhanced ServerTest.HttpSexy404HtmlError
unit-test, resulting in a clean test-suite.
2025-05-14 21:30:05 +04:00
Veloman Yunkan
6a0349e575 Preparing ServerTest.HttpSexy404HtmlError for translation 2025-05-14 21:30:02 +04:00
Veloman Yunkan
6d80edc04a Translated the advice on the new 404 error page
Translation of the multi-line/multi-paragraph advice is done under the
assumption that its structure  (5 paragraphs, two of which serve as
entries in a bulleted list) can be preserved in the translations by
proper phrasing, i.e. the advice must be translated as a whole rather
than each of its sentences (which act as units of translation) separately.
2025-05-14 21:27:56 +04:00
Veloman Yunkan
b3a33747f0 More simple translations on the new 404 error page 2025-05-14 21:25:59 +04:00
Veloman Yunkan
f47490e1bc Started translation of the new 404 error page
- Enabled translation on the new 404 error page
- Translated its title & heading

This commit also fixes the failure of the ServerTest.UserLanguageControl
unit-test.

The only remaining failing test case is ServerTest.Http404HtmlError
(only for url=/ROOT%23%3F/content/zimfile/invalid-article?userlang=test).
2025-05-14 21:23:34 +04:00
Veloman Yunkan
1ce909ae68 ServerTest.HttpSexy404HtmlError unit-test
Converted a few failing test points of the ServerTest.Http404HtmlError
unit-test into a new unit-test ServerTest.HttpSexy404HtmlError.

Some broken test cases still remain.
2025-05-14 21:20:00 +04:00
Veloman Yunkan
5eb31d7286 New 404 error page
The page doesn't support translation, yet.

The new 404 error page is used only when accessing ZIM file content
(i.e. as a response from the `/content` API endpoint).

One notable difference from the previous error page is that now no hint
is provided about whether the error is due to trying to access a
non-existent book/ZIM-file or non-existent resource inside a valid
book/ZIM-file (previously such a hint was present in the suggested
search URL). However, when displayed in the viewer this difference can
be seen in the viewer toolbar - book related buttons are hidden if the
URL points to a non-existent book.

This change breaks some unit tests. They will be fixed in a separate
commit.
2025-05-14 21:18:36 +04:00
Veloman Yunkan
107421cdab Merge pull request #1179 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2025-05-10 17:21:29 +04:00
Veloman Yunkan
bd474b9720 Updated/regenerated static/skin/languages.js 2025-05-10 17:12:19 +04:00
translatewiki.net
e66ba1a532 Localisation updates from https://translatewiki.net. 2025-05-10 17:12:19 +04:00
Veloman Yunkan
c1e58331d7 Merge pull request #1194 from kiwix/linux_ci_runner_upgrade
Upgraded Linux CI runner to ubuntu-22.04
2025-05-10 17:11:57 +04:00
Veloman Yunkan
d776077c5f Upgraded Linux CI runner to ubuntu-22.04
We are quite late with this change and were simply forced to make it
because ubuntu-20.04 has been phased out in GitHub Workflows.
2025-05-10 17:00:52 +04:00
Kelson
b8e997f805 Merge pull request #1190 from kiwix/pastproof_atomics_check
Made atomics check work with old compilers
2025-04-02 18:47:21 +02:00
Veloman Yunkan
b7421d7dae Made atomics check work with old compilers
In our CI a quite old version of gcc (6.3.0) is used for the aarch64
configs and it was confused by the (previous) code of the test program
intended to find out if libatomics must be explicitly passed to the
linker.
2025-04-02 15:10:46 +04:00
Kelson
a0c99f879b Merge pull request #1183 from Optimus-NP/kiwix-tools-issue_731
Show spinner while loading ZIM content in viewer iframe
2025-03-30 19:51:09 +02:00
Naman Pahwa
c7e86c9dbb Show spinner while loading ZIM content in viewer iframe
- Implemented a spinner to improve user experience while ZIM content is loading in the viewer iframe.
- Added .loader and .spinner styles in kiwix.css.
- The iframe content is initially hidden (visibility: hidden) and will be displayed once loading completes.
- Used CSS animations (@keyframes spin) for a smooth rotating effect.
2025-03-26 21:51:43 +05:30
Kelson
ad58a501b0 Merge pull request #1181 from Optimus-NP/kiwix-tools_issues_724
Replace multiple comma-separated languages with 'mul'
2025-03-17 18:55:07 +01:00
Naman Pahwa
a55e8565d1 Replace multiple comma-separated languages with 'mul'
- Refactored language code handling to replace multiple comma-separated values with 'mul'.
- Single-language entries remain unchanged.
- created the helper function to get the lang tag
- ensured the consistent behaviour for js and nojs version for the kiwix library view
2025-03-17 21:10:53 +05:30
Kelson
610b8cbb2a Merge pull request #1177 from kiwix/handling_of_pdf_links_under_chrome_on_android
If PDF viewer is not enabled PDFs are downloaded instead
2025-02-07 05:35:53 +01:00
Veloman Yunkan
e087f1c82f If PDF viewer is not enabled, PDFs are downloaded
Chrome on Android doesn't support displaying PDF documents inline so an
attempt to load a PDF into the Kiwix viewer iframe fails in a way that
may be confusing to the users. In such situations it is better to offer
to the users to download the PDF file so that they can view it with a
dedicated application. Making the clicked PDF link to open in a new
tab/window achieves exactly that effect.
2025-02-07 05:32:30 +01:00
Kelson
e5d3e6ff07 Merge pull request #1170 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2025-02-07 05:32:01 +01:00
translatewiki.net
d0aeac64d0 Localisation updates from https://translatewiki.net. 2025-02-06 13:05:48 +01:00
Kelson
5da88c0ad7 Merge pull request #1176 from kiwix/handling_of_target_blank_external_links
Handling of external links with target="_blank"
2025-02-03 14:44:15 +01:00
Veloman Yunkan
c7d3a38a3e Handling of external links with target="_blank"
External links with target="_blank" attribute are opened in a new tab/window.

Also included metaKey (Command key under Mac) in the list of click
modifiers that make the link to be opened in a new tab.
2025-02-03 17:11:29 +04:00
Kelson
6b74395455 Merge pull request #1175 from kiwix/fully_visible_suggestions
Autocompleter box size matches the content width
2025-02-03 13:25:35 +01:00
Veloman Yunkan
2eea6136d6 Full suggestion text is shown on hover
The width of the suggestion box is capped at 600 pixels. The full
text of a suggestion is shown on hover (doesn't work on mobile).
2025-02-03 16:11:53 +04:00
Veloman Yunkan
64eb0d10d6 Autocompleter box size matches the content width
Note however that no upper limit is set on the width of the
autocompleter box - it is possible, but I don't see how we could
come up with a good value for it.
2025-01-27 17:20:39 +04:00
Kelson
664944f16c Merge pull request #1174 from OlCe2/main
meson.build: Comment on 'threads' dependency required for FreeBSD
2025-01-15 17:51:23 +01:00
Olivier Certner
d34a0c5bf0 meson.build: Comment on 'threads' dependency required for FreeBSD
While here, wrap the long test line.
2025-01-13 18:43:28 +01:00
Kelson
bb65d77229 Merge pull request #1169 from kiwix/wait-1s-to-aria2c
Wait up to 1s to le aria2c to start
2025-01-12 12:17:56 +01:00
Emmanuel Engelhart
98849831da Wait up to 1s to le aria2c to start 2025-01-12 12:17:10 +01:00
Kelson
2e3eae5615 Merge pull request #1173 from OlCe2/main
Two compilation fixes for FreeBSD
2025-01-08 16:47:14 +01:00
Olivier Certner
93ace5cf45 networkTools: Fix compilation on FreeBSD
Header <netinet/in.h> should be included (as per POSIX) to get a definition for
'struct sockaddr_in'.  Fixes commit "add ipv6 support to HTTP daemon".
2025-01-08 15:07:30 +01:00
Olivier Certner
cb777ed836 meson.build: Test whether linking with 'libatomic' is necessary
See the added comments for more details.

While here, initialize 'extra_deps' once and for all at the top and append to it
when needed.
2025-01-08 15:07:00 +01:00
Kelson
27e7840cce Merge pull request #1158 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-12-20 10:18:53 +01:00
translatewiki.net
99c28b72b5 Localisation updates from https://translatewiki.net. 2024-12-20 10:11:36 +01:00
Kelson
81b579cdcb Merge pull request #1168 from kiwix/remove-set-output-in-workflows
Update a few dependencies in the workflows
2024-12-20 10:08:08 +01:00
Emmanuel Engelhart
f693f700bc Remove HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 2024-12-20 09:13:26 +01:00
Emmanuel Engelhart
50f04d7060 'pkg-config' is already installed with proper version 2024-12-20 09:08:35 +01:00
Emmanuel Engelhart
8fdaa5f4db Bump-up to actions/checkout@v4 2024-12-20 08:31:44 +01:00
Emmanuel Engelhart
297627fbc7 Bump-up to actions/upload-artifact@v4 2024-12-20 08:31:44 +01:00
Emmanuel Engelhart
a3708c68ce Replace deprecated 'set-output' in workflows 2024-12-20 08:31:43 +01:00
Kelson
a809c671fd Merge pull request #1167 from kiwix/better-deb-control-version-checking
Check versus minor version of dependencies
2024-12-20 08:29:03 +01:00
Emmanuel Engelhart
31477bc99b Check versus minor version of dependencies 2024-12-20 08:19:45 +01:00
Kelson
6c37e2827e Merge pull request #1165 from kiwix/kelson42-patch-1
Stop building Windows with DEBUG symbols in CI
2024-11-26 08:37:20 +00:00
Kelson
9138f91c31 Stop building Windows with DEBUG symbols in CI 2024-11-26 09:35:55 +01:00
rgaudin
9bd568fe0e Merge pull request #1160 from kiwix/1157_fixmagnet
magnetLink queryString to start with ? and not ?&
2024-11-08 16:27:46 +00:00
rgaudin
eca7cf86e6 fixup! Fixed #1157: magnetLink queryString to start with ? and not ?& 2024-11-08 15:56:58 +00:00
rgaudin
77f4fd7447 Fixed #1157: magnetLink queryString to start with ? and not ?& 2024-11-08 14:58:28 +00:00
Kelson
84ebee899c Merge pull request #1154 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-10-31 11:37:34 +01:00
translatewiki.net
9eed5da3be Localisation updates from https://translatewiki.net. 2024-10-28 13:07:08 +01:00
Kelson
20abebd623 Merge pull request #1152 from kiwix/macos13
removed temp hack
2024-10-09 17:18:52 +00:00
rgaudin
58a1af85b9 removed temp hack and unlink 2024-10-09 14:38:25 +00:00
Kelson
585f55d885 Merge pull request #1151 from kiwix/feature/fix-server-set-addr
Fix Server::setAddress
2024-10-09 14:24:46 +00:00
sgourdas
8b00c2eb22 Fix Server::setAddress 2024-10-09 12:17:01 +03:00
Kelson
c8bddd6cf4 Merge pull request #1149 from kiwix/feature/remove-ip-prefix
Remove 169.254 prefix
2024-10-09 05:25:16 +00:00
sgourdas
5d1b6274a8 Remove 169.254 prefix 2024-10-08 21:39:12 +03:00
Kelson
0de9bd0a99 Merge pull request #1141 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-10-08 16:15:40 +00:00
translatewiki.net
b62274efdd Localisation updates from https://translatewiki.net. 2024-10-07 14:06:39 +02:00
Kelson
4a1498d8df Merge pull request #1145 from kiwix/ci_fix
Fix broken CI on Linux (aarch64, focal)
2024-10-03 16:48:21 +00:00
Veloman Yunkan
f6df2342cf Fix broken CI on Linux (aarch64, focal) 2024-10-03 17:13:34 +04:00
Kelson
8bbda99cab Merge pull request #1132 from kiwix/feature/best-public-ip
Refactor getBestPublicIp for all valid ips
2024-10-02 19:31:17 +00:00
sgourdas
95529d2c0a Add ip availability check in server start 2024-10-02 20:55:13 +03:00
sgourdas
b80699916d Refactor getBestPublicIp for both protocols 2024-10-02 20:55:11 +03:00
sgourdas
534916929d Line cleanup 2024-09-30 19:39:40 +03:00
sgourdas
02ab2ce5a5 Make server getters const 2024-09-30 19:39:40 +03:00
sgourdas
8930095c52 Make IpMode members uppercase 2024-09-30 19:39:40 +03:00
Kelson
bef3ec7694 Merge pull request #1140 from kiwix/release-14.0.0
libkiwix 14.0.0 Changelog
2024-09-28 14:36:36 +00:00
Emmanuel Engelhart
9057686a25 libkiwix 14.0.0 Changelog 2024-09-28 14:29:07 +00:00
Kelson
723dd977fe Merge pull request #1126 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-09-28 14:28:07 +00:00
translatewiki.net
0b87d4fe04 Localisation updates from https://translatewiki.net. 2024-09-26 14:07:55 +02:00
Kelson
ea31e2f42f Merge pull request #1137 from kiwix/catalog_search_without_boolean_operators
Catalog search without support for Xapian boolean operators
2024-09-25 15:16:53 +00:00
Veloman Yunkan
01bda6b2c0 Disabled Xapian boolean operators in catalog query 2024-09-25 17:42:50 +04:00
Veloman Yunkan
de64a5a724 Testing of Xapian query operators in catalog search 2024-09-25 17:42:50 +04:00
Veloman Yunkan
90dd1cb3f0 Enhanced test book descriptions with xapian keywords
Added to test book descriptions words that serve as keywords
for query syntax with boolean operators (or, and, not, xor, near, adj) enabled.

Note that the change in indexed text has lead to the change in the order
of returned results.
2024-09-25 16:41:38 +04:00
Kelson
0b14fda94d Merge pull request #1135 from kiwix/fix-deb-package-deps
Fix deb package dependencies
2024-09-18 12:39:34 +00:00
Emmanuel Engelhart
fe965faf1b Fix deb package dependencies 2024-09-18 14:21:42 +02:00
Kelson
6ad1776242 Merge pull request #1133 from kiwix/pkgconfig
Generation of libkiwix.pc via meson's pkgconfig module
2024-09-17 10:35:29 +02:00
Veloman Yunkan
cbfd3ec7c4 Dropped generation of kiwix.pc
Below are the contents of libkiwix.pc and kiwix.pc files generated using
the new and old approaches, respectively, for native_dyn and
native_static configurations:

```
$ cat BUILD_native_dyn/INSTALL/lib/x86_64-linux-gnu/pkgconfig/libkiwix.pc
prefix=<REDACTED>
includedir=${prefix}/include
libdir=${prefix}/lib/x86_64-linux-gnu

Name: libkiwix
Description: A library that contains useful primitives that Kiwix readers have in common
Version: 14.0.0
Requires.private: icu-i18n, libzim < 10.0.0, libzim >= 9.0.0, pugixml, libcurl, libmicrohttpd, zlib, xapian-core
Libs: -L${prefix}/lib/x86_64-linux-gnu -lkiwix
Libs.private: -pthread
Cflags: -I${includedir}

$ cat BUILD_native_dyn/INSTALL/lib/x86_64-linux-gnu/pkgconfig/kiwix.pc
prefix=<REDACTED>
libdir=${prefix}/lib64
includedir=${prefix}/include

Name: libkiwix
Description: A library that contains a lot of things used by used by other kiwix programs
Version: 14.0.0
Requires: libzim icu-i18n pugixml libcurl libmicrohttpd xapian-core
Libs: -L${libdir} -lkiwix
Cflags: -I${includedir}/

$ cat BUILD_native_static/INSTALL/lib/x86_64-linux-gnu/pkgconfig/libkiwix.pc
prefix=<REDACTED>
includedir=${prefix}/include
libdir=${prefix}/lib/x86_64-linux-gnu

Name: libkiwix
Description: A library that contains useful primitives that Kiwix readers have in common
Version: 14.0.0
Requires: icu-i18n, libzim < 10.0.0, libzim >= 9.0.0, pugixml, libcurl, libmicrohttpd, zlib, xapian-core
Libs: -L${prefix}/lib/x86_64-linux-gnu -lkiwix -pthread
Cflags: -I${includedir} -pthread

$ cat BUILD_native_static/INSTALL/lib/x86_64-linux-gnu/pkgconfig/kiwix.pc
prefix=<REDACTED>
libdir=${prefix}/lib64
includedir=${prefix}/include

Name: libkiwix
Description: A library that contains a lot of things used by used by other kiwix programs
Version: 14.0.0
Requires: libzim icu-i18n pugixml libcurl libmicrohttpd xapian-core
Libs: -L${libdir} -lkiwix
Cflags: -I${includedir}/
```

The notable differences are:

- libdir changed from `${prefix}/lib64` to `${prefix}/lib/x86_64-linux-gnu`

- for native_dyn configuration Requires.private is used

- pthread has appeared in Libs/Libs.private and/or Cflags

- version information was added to the libzim requirement
2024-09-17 12:26:14 +04:00
Veloman Yunkan
f6765137e7 Fixed the libzim version requirement
Also fixed capitalization in an error message
2024-09-16 17:26:28 +04:00
Veloman Yunkan
c24e04c8da Generation of libkiwix.pc via meson's pkgconfig module
Generation of kiwix.pc using the previous approach still stays in place.
2024-09-16 17:25:42 +04:00
Kelson
327fec1877 Merge pull request #1131 from kiwix/ungarbled_binary_resources
Ungarbled binary resources
2024-09-14 14:43:23 +02:00
Veloman Yunkan
c8524b95bc Protection against adding resources of new types
Now if a static resource of a new type is added the build will
fail unless the list of known file type extensions is updated.
2024-09-12 17:39:49 +04:00
Veloman Yunkan
0ac3130b0d Added an extension to an extensionless resource
... so that all resources have extensions and can be automatically
categorized as binary or text based on extension (coming next).
2024-09-12 17:18:16 +04:00
Veloman Yunkan
425ae1efae Disabled dos2unix conversion for binary resources 2024-09-11 17:52:11 +04:00
Veloman Yunkan
920d603a89 Validation of resource content against cacheid
Added a test that checks that the static resources returned by the
server have content that matches their cacheid. This test currently
fails because some binary resources (e.g. png images) are garbled by the
dos2unix conversion.
2024-09-11 17:46:40 +04:00
Kelson
f5c91cc272 Merge pull request #1094 from kiwix/downloadBtn
More conspicuous download button
2024-09-11 11:42:59 +02:00
Veloman Yunkan
3cdc036858 Updated nojs library page
... to keep it in sync with the JSful library page that has been
modified in the previous commit.
2024-09-11 13:17:27 +04:00
Nikhil Tanwar
29bfaa5c5b Move download button to end of tile
- Changed the position of download button to the end of tile and
  added a proper download icon to it. When the button is hovered it
  becomes darker.

- Also internationalized the "Download" text on the modal download widget
  and added download size information to it.
2024-09-11 11:02:08 +04:00
Kelson
bec80e8091 Merge pull request #1123 from kiwix/handling_of_external_app_links
Handling of external app links in the viewer
2024-09-10 13:45:50 +00:00
Veloman Yunkan
2da9801bac Fixed ctrl-clicking of links for Zimit2 ZIMs
Zimit2 ZIMs employ Wombat for client-side rewriting of URLs. The latter
interferes with our approach of handling in the viewer CTRL/SHIFT-clicks
on links inside articles. This commit disables Wombat temporarily while
changing the href attribute of the clicked link.
2024-09-10 17:03:04 +04:00
Veloman Yunkan
16ebc6611b Handling of external app links in the viewer
Links that should be handled/opened by external applications - such as email
addresses (mailto:), phone numbers (tel:), etc - are opened by the
viewer in a new tab/window, thus avoiding any issues with content
security policy.
2024-09-10 16:28:28 +04:00
Kelson
d5a44b913e Merge pull request #1127 from kiwix/crisp_illustration_on_book_tile
Illustration size on book tile is fixed to 48x48
2024-09-09 18:03:57 +00:00
Veloman Yunkan
a63a162c58 Illustration size on book tile is fixed to 48x48 2024-09-09 17:47:55 +00:00
Kelson
c29cd8cf3b Merge pull request #1128 from kiwix/mul_as_mul
Deprived pseudolang mul of a self-name
2024-09-09 15:03:47 +00:00
Veloman Yunkan
04bf1be9d6 Deprived pseudolang mul of a self-name
ICU package contains a special code "mul" with a self-name of "multiple
languages". libkiwix now suppresses that. As a result the self-name of
"mul" (like for any other unknown language code) is the code itself
(i.e. "mul").

The most prominent user-visible effect of this change is that the
language filter in the library page no longer contains a "Multiple
languages" entry if there is a legacy ZIM file with the language set to
"mul" - that entry now shows up as "Mul".
2024-09-09 16:57:07 +04:00
Kelson
59054aa5ad Merge pull request #1109 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-09-08 20:49:31 +00:00
translatewiki.net
1b8dde0115 Localisation updates from https://translatewiki.net. 2024-09-08 19:34:42 +00:00
Kelson
9d0f6a3170 Merge pull request #1125 from kiwix/remove-windows-cross-compile
Remove Windows cross-compilation from CI
2024-09-08 19:34:27 +00:00
Emmanuel Engelhart
c16ed0aa4c Remove Windows cross-compilation from CI 2024-09-08 21:18:16 +02:00
Kelson
3d95b386c6 Merge pull request #1124 from kiwix/better-libzim-version-check
Better check libzim version
2024-09-07 13:43:37 +00:00
Emmanuel Engelhart
a3f5a654f2 Better check libzim version 2024-09-07 15:35:30 +02:00
Kelson
801b1df304 Merge pull request #1121 from kiwix/safe_tags_without_makeup
Untransformed (but HTML-safe) tags in the kiwix-serve frontend
2024-09-05 04:43:29 +00:00
Veloman Yunkan
2b8a071c6f Fixed cacheids in unit-tests 2024-09-04 19:13:41 +04:00
Veloman Yunkan
00fae37f2d Fixed vertical alignment of the tag filter indicator 2024-09-04 19:09:35 +04:00
Veloman Yunkan
846404e959 Proper HTML encoding/decoding of tags in the frontend
- Tags in the OPDS feed are HTML encoded and must be decoded.

- Tag values must be HTML encoded when injected into the DOM:

  * When the injection is done by setting the innerHTML attribute of a
    DOM element, HTML encoding must be done explicitly (since that text
    is going to be parsed as HTML).

  * When the tag value is expanded into a string that is then set as an
    attribute of a DOM element via the setAttribute() method, no HTML
    encoding must be done (since Element.setAttribute() directly sets
    that value and no HTML decoding is involved in that operation).
2024-09-04 18:43:24 +04:00
Veloman Yunkan
fbcd160efd Disabled beautification of tags in the frontend 2024-09-04 18:15:01 +04:00
Kelson
196185dd73 Merge pull request #1119 from kiwix/fix-debian-control-file-for-v14
Fix deb control file for 14.0.0
2024-09-02 18:14:23 +00:00
Emmanuel Engelhart
affb996769 Fix deb control file for 14.0.0 2024-09-02 19:59:27 +02:00
Kelson
418abbcefa Merge pull request #1116 from kiwix/bump-up-to-version-14.0.0
Bump-up version to 14.0.0
2024-09-01 12:47:13 +00:00
Emmanuel Engelhart
00867a13f6 Rename deb .install file 2024-09-01 14:39:45 +02:00
Emmanuel Engelhart
e096c7e2fd Bump-up version to 14.0.0 2024-09-01 10:48:49 +02:00
Kelson
69341eab47 Merge pull request #1114 from kiwix/update-deb-packages-ci-cd
Update deb packages CI/CD
2024-09-01 08:41:30 +00:00
Emmanuel Engelhart
082727ebb6 Comment out Debian related CI/CD 2024-09-01 10:31:19 +02:00
Emmanuel Engelhart
75a4f8b806 Requires libzim9 2024-09-01 10:14:03 +02:00
Emmanuel Engelhart
2eaa1c4649 Refresh deb packages CI/CD 2024-09-01 10:06:31 +02:00
Matthieu Gautier
199a10d093 Merge pull request #1113 from kiwix/ci_windows 2024-08-27 13:25:40 +02:00
Matthieu Gautier
4812fb18f6 Ensure resources use \n as newline and not \r\n
It appears that git on Windows replace `\n` with `\r\n` in the working
tree.

So compiled resources contain a extra `\r` and our tests are failing.
2024-08-27 13:17:53 +02:00
Matthieu Gautier
940818d801 Do not do iterator underflow in urlDecode
`value.end() - 3` may be "before start of string" if string length is < 3.
On Windows debug, is throw an exception.
On other platform it is probably an undefined behavior.

Rewrite the test to avoid such invalid substraction.
2024-08-27 13:17:53 +02:00
Matthieu Gautier
5182a66b19 Update test to have Windows paths on Windows 2024-08-27 13:17:53 +02:00
Matthieu Gautier
b688aa294a [CI] Build libkiwix on Windows CI
As kiwix-build now publish deps archive for libkiwix on Windows (github actions),
we can now start compiling (and test !!) libkiwix on Windows CI.
2024-08-22 17:04:54 +02:00
Kelson
27ad77c566 Merge pull request #1111 from kiwix/revert-1110-revert-1107-feature/data-directory
"Removed getDataDirectory()" again
2024-08-21 23:50:24 +02:00
Matthieu Gautier
7677f76854 Revert "Revert "Removed getDataDirectory()"" 2024-08-21 22:58:42 +02:00
Kelson
513a8d1383 Merge pull request #1105 from kiwix/string_slugification
Add String Slugification for Generating File Name
2024-08-14 20:24:08 +00:00
ShaopengLin
be464a5986 Introduce getSlugifiedFileName in tools.h
The function sanitizes file names depending on OS.
2024-08-14 20:11:11 +00:00
Kelson
c2042c3be8 Merge pull request #1112 from kiwix/public_i18n_api
Public i18n API
2024-08-14 11:18:29 +00:00
Veloman Yunkan
8d480c8b6d Introduced translateBookCategory() 2024-08-14 12:45:37 +04:00
Veloman Yunkan
82ff88f5d8 Made some i18n API public 2024-08-14 12:45:37 +04:00
Veloman Yunkan
2535f210b3 Renamed src/server/{i18n -> i18n_utils}.h
... so that i18n.h can be introduced in include/
2024-08-14 12:44:20 +04:00
Matthieu Gautier
cb0a2c234a Merge pull request #1110 from kiwix/revert-1107-feature/data-directory 2024-08-12 16:32:21 +02:00
Matthieu Gautier
5a73a75798 Revert "Removed getDataDirectory()" 2024-08-12 16:22:01 +02:00
Matthieu Gautier
0ea756c42a Merge pull request #1107 from kiwix/feature/data-directory 2024-08-12 14:41:33 +02:00
sgourdas
7108dfa9c2 Remove makeDirectory 2024-08-11 23:46:48 +03:00
sgourdas
9fd8e81de2 Remove getDataDirectory 2024-08-11 23:46:34 +03:00
sgourdas
566b40a2f8 Pass download directory directly to startDownload 2024-08-11 23:45:27 +03:00
Kelson
ff6d8a4b30 Merge pull request #1108 from kiwix/pkgconf
Replace pkg-config by pkgconf package in deb
2024-08-07 05:03:38 +00:00
Emmanuel Engelhart
f456ce3e93 Replace pkg-config by pkgconf 2024-08-07 06:53:25 +02:00
Veloman Yunkan
ece40966f1 Merge pull request #1097 from kiwix/robust_download_management
kiwix::Downloader's constructor pauses all downloads found in the aria session file
2024-07-08 20:26:24 +04:00
Veloman Yunkan
65a777d4ed Active downloads are paused before starting aria2c
The aria session file is edited before starting aria2c. This ensures
responsive aria2c RPC server even after a crash.
2024-07-08 20:17:07 +04:00
Veloman Yunkan
42295c9010 Defense against non-responsive aria RPC on startup
Downloader constructor may get stuck if the check for the aria2c RPC
being up gets stuck due to curl_easy_perform() never returning (or, at
least, taking longer than I was willing to wait). Currently it may
happen, for example, after an application crashes with active downloads
being saved to slow media. Then the next creation of a Downloader object
will deal with aria2c immediately resuming those downloads and becoming
unresponsive as it struggles flushing incoming data to slow storage (or
because of some other unfortunate timing of the RPC request being
received while it cannot yet be served).
2024-07-08 20:17:07 +04:00
Veloman Yunkan
e8afcbe6ae Downloader::close() is called in destructor 2024-07-08 20:17:07 +04:00
Veloman Yunkan
c46cd403ae Downloader::close() pauses all downloads
Otherwise, creating a Downloader object next time may take very long (or
that operation may get stuck) if an active download is being saved to slow
media.
2024-07-08 20:17:07 +04:00
Kelson
af96b19bd1 Merge pull request #1091 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-06-30 13:38:26 +02:00
translatewiki.net
8a00e9383d Localisation updates from https://translatewiki.net. 2024-06-27 14:07:38 +02:00
Veloman Yunkan
964131ce47 Merge pull request #1096 from harsha-mangena/i1095-consistancy-issue
[fixes]ZIM size being advertized inconsistently between MB and MiB
2024-06-24 12:50:29 +04:00
harsha-mangena
97832c8436 Book sizes are shown in binary (MiB, etc) units
Switched display of ZIM sizes from decimal (MB) to binary (MiB) units.

Also made the size value to be formatted in a more human friendly way
(fractional part is shown only to provide at least three significant
digits).
2024-06-24 12:40:49 +04:00
Kelson
beab8d7041 Merge pull request #1093 from kiwix/network_tools_backward_compatibility
Backward compatible support for IPv6
2024-06-11 11:27:05 +02:00
Veloman Yunkan
75bddbf725 "Unittests" for getBestPublicIp() & getNetworkInterfaces()
The unit-tests only call the said functions and print their output
which should then be examined by the maintainer.
2024-06-11 11:19:49 +02:00
Veloman Yunkan
5927550a36 kiwix::getNetworkInterfacesIPv4Or6()
- Restored kiwix::getNetworkInterfaces() API to the version before
  support for IPv6 was introduced

- Renamed the new API method to kiwix::getNetworkInterfacesIPv4Or6()
2024-06-11 11:19:49 +02:00
Veloman Yunkan
135c6f875d Hid some symbols in unnamed namespace 2024-06-11 11:19:49 +02:00
Veloman Yunkan
83101679a0 Backward compatible overload of getBestPublicIp() 2024-06-11 11:19:49 +02:00
Matthieu Gautier
ae4b652fb2 Merge pull request #1092 from kiwix/fix_extra_lib_args 2024-06-03 16:06:43 +02:00
Matthieu Gautier
01b94418eb Fix wrong usage of extra_link_args variable. 2024-06-03 14:44:31 +02:00
Kelson
a1ce3d10b1 Merge pull request #1074 from aryanA101a/main
Add IPv6 support to HTTP daemon
2024-05-29 21:10:13 +02:00
Aryan Arora
b7eadf95bf handle ip modes & add compilation flags for windows build 2024-05-29 21:05:01 +02:00
Aryan Arora
a6cf161341 add ipv6 support to HTTP daemon 2024-05-29 21:05:01 +02:00
Veloman Yunkan
618a718645 Merge pull request #1073 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-05-27 18:01:00 +04:00
Veloman Yunkan
c2cc4c39f1 Registered i18n resources for new languages 2024-05-27 17:41:29 +04:00
translatewiki.net
8477e04ffa Localisation updates from https://translatewiki.net. 2024-05-27 14:07:46 +02:00
Kelson
5345d43017 Merge pull request #1086 from kiwix/getdatadirectory-doc
Improve  documentation
2024-05-20 15:43:33 +02:00
Emmanuel Engelhart
843adb3397 Improve documentation 2024-05-20 15:41:31 +02:00
Kelson
4fe4a88574 Merge pull request #1088 from kiwix/better-kiwix-server-home-buttom-width
Better Kiwix Server taskbar home button max-width
2024-05-20 14:22:28 +02:00
Emmanuel Engelhart
6ee09114eb Better Kiwix Server taskbar home button max-width 2024-05-19 20:54:36 +02:00
Kelson
7366938785 Merge pull request #1079 from kiwix/raw-tag-displayed
Revert humanfriendly tag display in Kiwix Server
2024-05-12 17:59:06 +02:00
Emmanuel Engelhart
84405b1318 Revert humanfriendly tag disply in Kiwix Server 2024-05-12 17:54:34 +02:00
Kelson
a0c4118fd3 Merge pull request #1080 from kiwix/improve-custom-lang-mapping
Complete custom language mapping
2024-05-12 15:02:58 +02:00
Emmanuel Engelhart
72147aec5b Complete custom language mapping 2024-05-11 16:42:43 +02:00
Kelson
016072292c Merge pull request #1075 from kiwix/kiwix-server-accesskeys
Add Kiwix server a few accesskeys
2024-05-11 11:23:35 +02:00
Emmanuel Engelhart
2964cc5e92 Updates tests 2024-05-11 11:23:27 +02:00
Emmanuel Engelhart
8d766335b4 Add accesskey to Kiwix Server home button 2024-05-11 11:23:27 +02:00
Emmanuel Engelhart
5450bcd3c2 Add accesskey to Kiwix Server library button 2024-05-11 11:23:27 +02:00
Emmanuel Engelhart
a0b66eae0c Add accesskey to Kiwix Server random button 2024-05-11 11:23:27 +02:00
Emmanuel Engelhart
22c75245a5 Add accesskey to Kiwix Server multiple search inputs 2024-05-11 11:23:27 +02:00
Kelson
f40c3426a5 Merge pull request #1077 from kiwix/better-kiwix-server-download-overlay
Better labels/hints for Kiwix Server download overlay
2024-05-11 11:23:05 +02:00
Emmanuel Engelhart
8e6569362c Better labels/hints for Kiwix Server download overlay 2024-05-11 11:21:39 +02:00
Matthieu Gautier
eb328ed73d Merge pull request #1071 from kiwix/build_dir 2024-04-09 14:16:39 +02:00
Matthieu Gautier
21e3c5c19f New version of dl_deps_archive action doesn't need os_name. 2024-04-09 11:36:55 +02:00
Matthieu Gautier
f0927fec49 Build libkiwix with new archive from kiwix-build 2024-04-08 16:46:14 +02:00
Matthieu Gautier
66693cd73e Merge pull request #1070 from kiwix/new-kiwix-serve-head-link 2024-03-20 17:50:32 +01:00
Emmanuel Engelhart
be8a60c330 Add OpenSearch description in Kiwix Server homepage 2024-03-20 16:11:00 +01:00
Matthieu Gautier
8009edd349 Merge pull request #1066 from kiwix/smarter_startDownload 2024-03-20 10:52:56 +01:00
Veloman Yunkan
3733e506c1 More reasonable criteria for reusing a download 2024-03-20 11:26:18 +04:00
Veloman Yunkan
9fe81e9bce Introduced downloadCanBeReused() helper
The dubious logic of when an existing download can be reused by
Downloader::startDownload() is preserved.
2024-03-20 11:23:00 +04:00
Veloman Yunkan
4ab6215046 Downloader::Options typedef 2024-03-20 11:23:00 +04:00
Veloman Yunkan
ff88430227 Documented the problem with startDownload() 2024-03-20 11:23:00 +04:00
Veloman Yunkan
922c138809 Merge pull request #1064 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-03-09 16:08:48 +04:00
Veloman Yunkan
fa9ebf55fc Updated languages.js 2024-03-09 15:59:44 +04:00
translatewiki.net
bc9b5a0354 Localisation updates from https://translatewiki.net. 2024-03-07 13:07:14 +01:00
Matthieu Gautier
719e947ddf Merge pull request #1068 from kiwix/zim_name_vs_book_name 2024-03-06 15:09:39 +01:00
Veloman Yunkan
e3fffd9b23 Negative tests for books selection during search 2024-03-06 14:39:09 +01:00
Veloman Yunkan
6ef4f6396e Testing of filtering during search by books.filter.name 2024-03-06 14:39:09 +01:00
Veloman Yunkan
d8b4c1584c Testing of filtering during search by books.name 2024-03-06 14:39:09 +01:00
Veloman Yunkan
1fc006f639 Deduplicated test data in two test points
This was mainly done to prevent further duplication of test data as more
test points around the same query are added next but is also useful on
its own.
2024-03-06 14:39:09 +01:00
Matthieu Gautier
a8368b3a0d Merge pull request #1067 from kiwix/stricter_namemapper 2024-03-06 14:24:41 +01:00
Veloman Yunkan
068555de38 Paths in the error are put in single quotes 2024-03-06 11:43:43 +01:00
Veloman Yunkan
0168764f4c NameMapper detects all naming conflicts
Also this change leads to the change in the mapping (since conflicts
that previously went undetected and just overwrote the existing entry
are now rejected).
2024-03-06 11:43:43 +01:00
Veloman Yunkan
181893d31a Cleanup after previous change
- Got rid of the continue statement
- Renamed the function parameter
- Fixed indentation
2024-03-06 11:43:43 +01:00
Veloman Yunkan
5b9daf0d9d Extracted HumanReadableNameMapper::mapName()
No cleanup of the new function was performed to keep the diff minimal.
2024-03-06 11:43:43 +01:00
Veloman Yunkan
4e64d26ede Added more name conflicts to NameMapper unit test
The extended setup of the NameMapper unit test demonstrates (by the fact
that this change doesn't break the tests that check the stderr) that
certain naming conflicts escape NameMapper's attention.
2024-03-06 11:43:43 +01:00
Veloman Yunkan
5e669cd65c Deduplicated test output data 2024-03-06 11:43:43 +01:00
Veloman Yunkan
2749564424 Made entries in test library point to "different" files
Two entries in test library.xml used to point to the same file path.
Note that though the third entry pointed to a different file name
it is a symbolic link to the same file.

Now all three entries point to pseudo-different files (having the
same content behind them).

This change is needed so that tests don't break when detection of
conflicting book names is made stricter.
2024-03-06 11:43:43 +01:00
113 changed files with 3496 additions and 1016 deletions

View File

@@ -14,98 +14,146 @@ jobs:
os:
- macos-13
target:
- native_dyn
- iOS_arm64
- iOS_x86_64
- macos-x86_64-dyn
- ios-arm64-dyn
- ios-x86_64-dyn
include:
- target: macos-x86_64-dyn
arch_name: x86_64-apple-darwin
run_test: true
- target: ios-arm64-dyn
arch_name: aarch64-apple-ios
run_test: true
- target: ios-x86_64-dyn
arch_name: x86-apple-ios-simulator
run_test: true
runs-on: ${{ matrix.os }}
env:
HOME: /Users/runner
steps:
- name: Retrieve source code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install packages
run: |
brew update
brew unlink python3
# upgrade from python@3.12 to python@3.12.2 fails to overwrite those
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.12 /usr/local/bin/idle3 /usr/local/bin/idle3.12 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.12 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.12 /usr/local/bin/python3.12-config
brew install pkg-config ninja meson
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
brew install ninja meson
- name: Install dependencies
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
with:
os_name: macos
target_platform: ${{ matrix.target }}
- name: Compile
env:
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_${{matrix.target}}/INSTALL/lib/pkgconfig
CPPFLAGS: -I${{env.HOME}}/BUILD_native_dyn/INSTALL/include
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig
CPPFLAGS: -I${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/include
MESON_OPTION: --default-library=shared -Db_coverage=true
MESON_CROSSFILE: ${{env.HOME}}/BUILD_${{matrix.target}}/meson_cross_file.txt
MESON_CROSSFILE: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt
shell: bash
run: |
if [[ ! "${{matrix.target}}" =~ native_.* ]]; then
if [ -e $MESON_CROSSFILE ]; then
MESON_OPTION="$MESON_OPTION --cross-file $MESON_CROSSFILE -Dstatic-linkage=true"
fi
meson . build ${MESON_OPTION}
ninja -C build
- name: Test libkiwix
if: startsWith(matrix.target, 'native_')
if: matrix.run_test
env:
SKIP_BIG_MEMORY_TEST: 1
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_native_dyn/INSTALL/lib:${{env.HOME}}/BUILD_native_dyn/INSTALL/lib64
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib:${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib64
run: meson test -C build --verbose
Windows:
runs-on: windows-2022
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install packages
run:
choco install pkgconfiglite ninja
- name: Install python modules
run: pip3 install meson
- name: Setup MSVC compiler
uses: bus1/cabuild/action/msdevshell@v1
with:
architecture: x64
- name: Install dependencies
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
with:
target_platform: win-x86_64-static
- name: Compile
shell: cmd
run: |
set PKG_CONFIG_PATH=%cd%\BUILD_win-amd64\INSTALL\lib\pkgconfig
set CPPFLAGS=-I%cd%\BUILD_win-amd64\INSTALL\include
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=release
cd build
ninja.exe
- name: Test
shell: cmd
run: |
cd build
meson.exe test --verbose
env:
WAIT_TIME_FACTOR_TEST: 10
Linux:
strategy:
fail-fast: false
matrix:
name:
- native_static
- native_dyn
- android_arm
- android_arm64
- win32_static
- win32_dyn
target:
- linux-x86_64-static
- linux-x86_64-dyn
- android-arm
- android-arm64
include:
- name: native_static
target: native_static
- target: linux-x86_64-static
image_variant: focal
lib_postfix: '/x86_64-linux-gnu'
- name: native_dyn
target: native_dyn
arch_name: linux-x86_64
run_test: true
coverage: true
- target: linux-x86_64-dyn
image_variant: focal
lib_postfix: '/x86_64-linux-gnu'
- name: android_arm
target: android_arm
arch_name: linux-x86_64
run_test: true
coverage: true
- target: android-arm
image_variant: focal
lib_postfix: '/arm-linux-androideabi'
- name: android_arm64
target: android_arm64
arch_name: arm-linux-androideabi
run_test: false
coverage: false
- target: android-arm64
image_variant: focal
lib_postfix: '/aarch64-linux-android'
- name: win32_static
target: win32_static
image_variant: f35
lib_postfix: '64'
- name: win32_dyn
target: win32_dyn
image_variant: f35
lib_postfix: '64'
arch_name: aarch64-linux-android
run_test: false
coverage: false
env:
HOME: /home/runner
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
container:
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:38"
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install dependencies
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
with:
@@ -114,38 +162,40 @@ jobs:
shell: bash
run: |
meson --version
if [[ "${{matrix.target}}" =~ .*_dyn ]]; then
if [[ "${{matrix.target}}" =~ .*-dyn ]]; then
MESON_OPTION="--default-library=shared"
else
MESON_OPTION="--default-library=static"
fi
if [[ "${{matrix.target}}" =~ native_.* ]]; then
MESON_OPTION="$MESON_OPTION -Db_coverage=true"
if [ -e "${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt" ]; then
MESON_OPTION="$MESON_OPTION --cross-file ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt"
else
MESON_OPTION="$MESON_OPTION --cross-file $HOME/BUILD_${{matrix.target}}/meson_cross_file.txt"
MESON_OPTION="$MESON_OPTION -Db_coverage=true"
fi
if [[ "${{matrix.target}}" =~ android_.* ]]; then
if [[ "${{matrix.target}}" =~ android-.* ]]; then
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
fi
meson . build ${MESON_OPTION}
cd build
ninja
env:
PKG_CONFIG_PATH: "/home/runner/BUILD_${{matrix.target}}/INSTALL/lib/pkgconfig:/home/runner/BUILD_${{matrix.target}}/INSTALL/lib${{matrix.lib_postfix}}/pkgconfig"
CPPFLAGS: "-I/home/runner/BUILD_${{matrix.target}}/INSTALL/include"
PKG_CONFIG_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}/pkgconfig"
CPPFLAGS: "-I/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/include"
- name: Test
if: startsWith(matrix.target, 'native_')
if: matrix.run_test
shell: bash
run: |
cd build
meson test --verbose
ninja coverage
if [[ "${{matrix.coverage}}" = "true" ]]; then
ninja coverage
fi
env:
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.target}}/INSTALL/lib:/home/runner/BUILD_${{matrix.target}}/INSTALL/lib${{matrix.lib_postfix}}"
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}"
SKIP_BIG_MEMORY_TEST: 1
- name: Publish coverage
if: startsWith(matrix.target, 'native_')
if: matrix.coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -15,11 +15,16 @@ jobs:
fail-fast: false
matrix:
distro:
- debian-unstable
# - debian-unstable
# - debian-trixie
# - debian-bookworm
# - debian-bullseye
- ubuntu-noble
- ubuntu-jammy
- ubuntu-focal
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# Determine which PPA we should upload to
- name: PPA
@@ -27,25 +32,54 @@ jobs:
run: |
if [[ $REF == refs/tags* ]]
then
echo "::set-output name=ppa::kiwixteam/release"
echo "ppa=kiwixteam/release" >> $GITHUB_OUTPUT
else
echo "::set-output name=ppa::kiwixteam/dev"
echo "ppa=kiwixteam/dev" >> $GITHUB_OUTPUT
fi
env:
REF: ${{ github.ref }}
- uses: legoktm/gh-action-auto-dch@master
- uses: legoktm/gh-action-auto-dch@main
with:
fullname: Kiwix builder
email: release+launchpad@kiwix.org
distro: ${{ matrix.distro }}
- uses: legoktm/gh-action-build-deb@debian-unstable
if: matrix.distro == 'debian-unstable'
name: Build package for debian-unstable
id: build-debian-unstable
# - 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
#
# - uses: legoktm/gh-action-build-deb@b47978ba8498dc8b8153cc3b5f99a5fc1afa5de1 # pin@debian-trixie
# if: matrix.distro == 'debian-trixie'
# name: Build package for debian-trixie
# id: build-debian-trixie
# with:
# args: --no-sign
#
# - uses: legoktm/gh-action-build-deb@1f4e86a6bb34aaad388167eaf5eb85d553935336 # pin@debian-bookworm
# if: matrix.distro == 'debian-bookworm'
# name: Build package for debian-bookworm
# id: build-debian-bookworm
# with:
# args: --no-sign
#
# - uses: legoktm/gh-action-build-deb@084b4263209252ec80a75d2c78a586192c17f18d # pin@debian-bullseye
# if: matrix.distro == 'debian-bullseye'
# name: Build package for debian-bullseye
# id: build-debian-bullseye
# with:
# args: --no-sign
- uses: legoktm/gh-action-build-deb@9114a536498b65c40b932209b9833aa942bf108d # pin@ubuntu-noble
if: matrix.distro == 'ubuntu-noble'
name: Build package for ubuntu-noble
id: build-ubuntu-noble
with:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
if: matrix.distro == 'ubuntu-jammy'
@@ -63,12 +97,12 @@ jobs:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: Packages for ${{ matrix.distro }}
path: output
- uses: legoktm/gh-action-dput@master
- uses: legoktm/gh-action-dput@main
name: Upload dev package
# Only upload on pushes to main
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
@@ -77,7 +111,7 @@ jobs:
repository: ppa:kiwixteam/dev
packages: output/*_source.changes
- uses: legoktm/gh-action-dput@master
- uses: legoktm/gh-action-dput@main
name: Upload release package
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
with:

View File

@@ -1,3 +1,31 @@
libkiwix 14.0.0
===============
* Server:
- Support of IPv6 (@veloman-yunkan @aryanA101a #1074 #1093)
- Better public IP configuration/detection (@sgourdas #1132)
- Fix API errors in catalog searches if Xapian keyword in used (@veloman-yunkan #1137)
- Clearly define which Web browsers are supported (@kelson42 @rgaudin @jaifroid @benoit74 #1132)
- Improve welcome page download buttons (@veloman-yunkan #1094)
- Better handling of external (non-HTTP) links (@veloman-yunkan #1123)
- Fix book illustration size on welcome page to 48x48 pixels (@veloman-yunkan #1127)
- Remove "Multiple Languages" in language filter (@veloman-yunkan #1098)
- Stop transforming tags casing (@kelson42 @veloman-yunkan #1079 #1121)
- ZIM file size consistently advertised in MiB (@harsha-mangena #1132)
- Few new supported languages in the filter (@kelson42 #1080)
- Improve accesskeys (@kelson42 #1075)
- Add OpenSearch <link> to head of pages (@kelson42 #1070)
* Compilation/Packaging:
- Multiple fixes around deb packaging (@kelson42 #1108 #1114 #1135)
- Generating of libkiwix.pc via Meson (@veloman-yunkan #1133)
- Native Windows CI/CD (@mgautierfr @kelson42 #1113 #1125)
- Better check (maximum) libzim version (@kelson42 #1124)
- Multiple automated tests improvements (@veloman-yunkan #1068 #1067)
* Other:
- Deleted supported env. variable `$KIWIX_DATA_DIR` and `kiwix::getDataDirectory()` (@sgourdas #1107)
- New string slugification for filenames (@shaopenglin #1105)
- Multiple improvements around aria2c download mgmt. (@veloman-yunkan #1097)
libkiwix 13.1.0
===============

17
debian/control vendored
View File

@@ -3,13 +3,12 @@ Priority: optional
Maintainer: Kiwix team <kiwix@kiwix.org>
Build-Depends: debhelper-compat (= 13),
meson,
pkg-config,
libzim-dev (>= 7.2.0~),
pkgconf,
libzim-dev (>= 9.0), libzim-dev (<< 10.0),
libcurl4-gnutls-dev,
libicu-dev,
libgtest-dev,
libkainjow-mustache-dev,
liblzma-dev,
libmicrohttpd-dev,
libpugixml-dev,
zlib1g-dev
@@ -22,12 +21,13 @@ Package: libkiwix-dev
Section: libdevel
Architecture: any
Multi-Arch: same
Depends: libkiwix10 (= ${binary:Version}), ${misc:Depends}, python3,
libzim-dev (>= 7.2.0~),
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
libzim-dev (>= 9.0), libzim-dev (<< 10.0),
libicu-dev,
libpugixml-dev,
libcurl4-gnutls-dev,
libmicrohttpd-dev
libmicrohttpd-dev,
zlib1g-dev
Description: library of common code for Kiwix (development)
Kiwix is an offline Wikipedia reader. libkiwix provides the
software core for Kiwix, and contains the code shared by all
@@ -35,11 +35,12 @@ Description: library of common code for Kiwix (development)
.
This package contains development files.
Package: libkiwix10
Package: libkiwix14
Architecture: any
Multi-Arch: same
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
Conflicts: libkiwix0, libkiwix3, libkiwix9
Conflicts: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
Replaces: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
Description: library of common code for Kiwix
Kiwix is an offline Wikipedia reader. libkiwix provides the
software core for Kiwix, and contains the code shared by all

View File

View File

@@ -16,6 +16,7 @@
namespace kiwix {
enum class IpMode { IPV4, IPV6, ALL, AUTO }; // AUTO: Server decides the protocol
typedef zim::size_type size_type;
typedef zim::offset_type offset_type;

View File

@@ -168,8 +168,16 @@ class Download {
*/
class Downloader
{
public:
Downloader();
public: // types
typedef std::vector<std::pair<std::string, std::string>> Options;
public: // functions
/*
* Create a new Downloader object.
*
* @param sessionFileDir: The directory where aria2 will store its session file.
*/
explicit Downloader(std::string sessionFileDir);
virtual ~Downloader();
void close();
@@ -177,14 +185,22 @@ class Downloader
/**
* Start a new download.
*
* This method is thread safe and return a pointer to a newly created `Download`.
* This method is thread safe and returns a pointer to a newly created
* `Download` or an existing one with a matching URI. In the latter case
* the options parameter is ignored, which can lead to surprising results.
* For example, if the old and new download requests (sharing the same URI)
* have different values for the download directory or output file name
* options, after the download is reported to be complete the downloaded file
* will be present only at the location specified for the first request.
*
* User should call `update` on the returned `Download` to have an accurate status.
*
* @param uri: The uri of the thing to download.
* @param downloadDir: The download directory where the thing should be stored (takes precedence over any "dir" in `options`).
* @param options: A series of pair <option_name, option_value> to pass to aria.
* @return: The newly created Download.
*/
std::shared_ptr<Download> startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
std::shared_ptr<Download> startDownload(const std::string& uri, const std::string& downloadDir, Options options = {});
/**
* Get a download corrsponding to a download id (did)
@@ -206,7 +222,7 @@ class Downloader
*/
std::vector<std::string> getDownloadIds() const;
private:
private: // data
mutable std::mutex m_lock;
std::map<std::string, std::shared_ptr<Download>> m_knownDownloads;
std::shared_ptr<Aria2> mp_aria;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
* Copyright 2024 Veloman Yunkan <veloman.yunkan@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
@@ -17,29 +17,15 @@
* MA 02110-1301, USA.
*/
#ifndef KIWIX_SERVER_I18N
#define KIWIX_SERVER_I18N
#ifndef KIWIX_I18N
#define KIWIX_I18N
#include <map>
#include <string>
#include <mustache.hpp>
namespace kiwix
{
struct I18nString {
const char* const key;
const char* const value;
};
struct I18nStringTable {
const char* const lang;
const size_t entryCount;
const I18nString* const entries;
const char* get(const std::string& key) const;
};
std::string getTranslatedString(const std::string& lang, const std::string& key);
namespace i18n
@@ -70,28 +56,6 @@ private:
const std::string m_lang;
};
class GetTranslatedStringWithMsgId
{
typedef kainjow::mustache::basic_data<std::string> MustacheString;
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
public:
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
MsgIdAndTranslation operator()(const std::string& key) const
{
return {key, getTranslatedString(m_lang, key)};
}
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
{
return {key, expandParameterizedString(m_lang, key, params)};
}
private:
const std::string m_lang;
};
} // namespace i18n
class ParameterizedMessage
@@ -121,18 +85,8 @@ inline ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
return ParameterizedMessage(msgId, noParams);
}
struct LangPreference
{
const std::string lang;
const float preference;
};
typedef std::vector<LangPreference> UserLangPreferences;
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
std::string translateBookCategory(const std::string& lang, const std::string& category);
} // namespace kiwix
#endif // KIWIX_SERVER_I18N
#endif // KIWIX_I18N

View File

@@ -10,7 +10,8 @@ headers = [
'kiwixserve.h',
'name_mapper.h',
'tools.h',
'version.h'
'version.h',
'i18n.h'
]
install_headers(headers, subdir:'kiwix')

View File

@@ -54,6 +54,9 @@ class HumanReadableNameMapper : public NameMapper {
virtual ~HumanReadableNameMapper() = default;
virtual std::string getNameForId(const std::string& id) const;
virtual std::string getIdForName(const std::string& name) const;
private:
void mapName(const kiwix::Library& lib, std::string name, std::string id);
};
class UpdatableNameMapper : public NameMapper {

View File

@@ -22,6 +22,7 @@
#include <string>
#include <memory>
#include "tools.h"
namespace kiwix
{
@@ -51,7 +52,7 @@ namespace kiwix
void stop();
void setRoot(const std::string& root);
void setAddress(const std::string& addr) { m_addr = addr; }
void setAddress(const std::string& addr);
void setPort(int port) { m_port = port; }
void setNbThreads(int threads) { m_nbThreads = threads; }
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
@@ -62,14 +63,16 @@ namespace kiwix
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
void setBlockExternalLinks(bool blockExternalLinks)
{ m_blockExternalLinks = blockExternalLinks; }
int getPort();
std::string getAddress();
void setIpMode(IpMode mode) { m_ipMode = mode; }
int getPort() const;
IpAddress getAddress() const;
IpMode getIpMode() const;
protected:
std::shared_ptr<Library> mp_library;
std::shared_ptr<NameMapper> mp_nameMapper;
std::string m_root = "";
std::string m_addr = "";
IpAddress m_addr;
std::string m_indexTemplateString = "";
int m_port = 80;
int m_nbThreads = 1;
@@ -78,6 +81,7 @@ namespace kiwix
bool m_withTaskbar = true;
bool m_withLibraryButton = true;
bool m_blockExternalLinks = false;
IpMode m_ipMode = IpMode::AUTO;
int m_ipConnectionLimit = 0;
std::unique_ptr<InternalServer> mp_server;
};

View File

@@ -24,8 +24,17 @@
#include <vector>
#include <map>
#include <cstdint>
#include "common.h"
namespace kiwix
{
struct IpAddress
{
std::string addr; // IPv4 address
std::string addr6; // IPv6 address
};
namespace kiwix {
typedef std::pair<std::string, std::string> LangNameCodePair;
typedef std::vector<LangNameCodePair> FeedLanguages;
typedef std::vector<std::string> FeedCategories;
@@ -37,26 +46,6 @@ typedef std::vector<std::string> FeedCategories;
*/
std::string getCurrentDirectory();
/**
* Return the data directory.
*
* The data directory is a directory where to put data (zim files, ...)
* It depends of the platform and it may be changed by user using environment variable.
*
* The resolution order is :
* - `KIWIX_DATA_DIR` env variable (if set).
* - On Windows :
* . `$APPDATA/kiwix` if $APPDATA is set
* . `$USERPROFILE/kiwix` if $USERPROFILE is set
* - Else :
* . `$XDG_DATA_HOME/kiwix`if $XDG_DATA_HOME is set
* . `$HOME/.local/share/kiwx` if $HOWE is set
* - current directory
*
* @return the path of the data directory (utf8 encoded)
*/
std::string getDataDirectory();
/** Return the path of the executable
*
* Some application may be packaged in auto extractible archive (Appimage) and the
@@ -210,13 +199,30 @@ bool fileReadable(const std::string& path);
std::string getMimeTypeForFile(const std::string& filename);
/** Provides all available network interfaces
*
* This function provides the available IPv4 and IPv6 network interfaces
* as a map from the interface name to its IPv4 and/or IPv6 address(es).
*/
std::map<std::string, IpAddress> getNetworkInterfacesIPv4Or6();
/** Provides all available IPv4 network interfaces
*
* This function provides the available IPv4 network interfaces
* as a map from the interface name to its IPv4 address.
*
* Provided for backward compatibility with libkiwix v13.1.0.
*/
std::map<std::string, std::string> getNetworkInterfaces();
/** Provides the best IP address
* This function provides the best IP address from the list given by getNetworkInterfaces
* This function provides the best IP addresses for both ipv4 and ipv6 protocols,
* in an IpAddress struct, based on the list given by getNetworkInterfacesIPv4Or6()
*/
IpAddress getBestPublicIps();
/** Provides the best IPv4 adddress
* Equivalent to getBestPublicIp(false). Provided for backward compatibility
* with libkiwix v13.1.0.
*/
std::string getBestPublicIp();
@@ -231,15 +237,15 @@ 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.
* @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.
*/
@@ -247,10 +253,19 @@ 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);
/**
* Slugifies the filename by converting any characters reserved by the operating
* system to '_'. Note filename is only the file name and not a path.
*
* @param filename Valid UTF-8 encoded file name string.
* @return slugified string.
*/
std::string getSlugifiedFileName(const std::string& filename);
}
#endif // KIWIX_TOOLS_H

View File

@@ -1,10 +0,0 @@
prefix=@prefix@
libdir=${prefix}/lib64
includedir=${prefix}/include
Name: libkiwix
Description: A library that contains a lot of things used by used by other kiwix programs
Version: @version@
Requires: @requires@
Libs: -L${libdir} -lkiwix @extra_libs@
Cflags: -I${includedir}/ @extra_cflags@

View File

@@ -1,21 +1,49 @@
project('libkiwix', 'cpp',
version : '13.1.0',
version : '14.0.0',
license : 'GPLv3+',
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
compiler = meson.get_compiler('cpp')
static_deps = get_option('static-linkage') or get_option('default_library') == 'static'
extra_libs = []
# See https://github.com/kiwix/libkiwix/issues/371
if ['arm', 'mips', 'm68k', 'ppc', 'sh4'].contains(host_machine.cpu_family())
extra_libs = ['-latomic']
else
extra_libs = []
# Atomics as compiled by GCC or clang can lead to external references to
# functions depending on the type size and the platform. LLVM provides them in
# 'libcompiler_rt', which clang normally automatically links in, while GNU
# provides them in 'libatomic', which GCC *does not* link in automatically (but
# this is probably going to change, see
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81358). Regardless of the setup
# of the compiler driver itself (GCC or clang), we can thus assume that if some
# atomic references can't be resolved, then 'libatomic' is missing.
atomics_program = '''
#include <atomic>
#include <cstdint>
using namespace std;
int main() {
volatile atomic_bool a_b(true);
volatile atomic_ullong a_ull(-1);
// Next two lines are to cover atomic<socket_t> from 'httplib.h'.
volatile atomic<uint32_t> a_u32(-1);
volatile atomic<uint64_t> a_u64(-1);
return atomic_load(&a_b) == false && atomic_load(&a_ull) == 0 &&
atomic_load(&a_u32) == 0 && atomic_load(&a_u64) == 0;
}
'''
if not compiler.links(atomics_program,
name: 'compiler driver readily supports atomics')
libatomic = compiler.find_library('atomic')
compiler.links(atomics_program, name: 'atomics work with libatomic',
dependencies: libatomic, required: true)
extra_libs += ['-latomic']
endif
if (compiler.get_id() == 'gcc' and build_machine.system() == 'linux') or host_machine.system() == 'freebsd'
# C++ std::thread is implemented using pthread on linux by gcc
# C++ std::thread is implemented using pthread on Linux by GCC, and on FreeBSD
# for both GCC and LLVM.
if (host_machine.system() == 'linux' and compiler.get_id() == 'gcc') or \
host_machine.system() == 'freebsd'
thread_dep = dependency('threads')
else
thread_dep = dependency('', required:false)
@@ -35,9 +63,10 @@ else
error('Cannot found header mustache.hpp')
endif
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
libzim_dep = dependency('libzim', version:['>=9.0.0', '<10.0.0'], static:static_deps)
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
endif
@@ -49,8 +78,14 @@ endif
if host_machine.system() == 'windows'
add_project_arguments('-DNOMINMAX', language: 'cpp')
extra_libs += ['-liphlpapi']
endif
if build_machine.system() == 'windows'
extra_libs += ['-lshlwapi', '-lwinmm']
endif
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
inc = include_directories('include', extra_include)
@@ -58,12 +93,6 @@ inc = include_directories('include', extra_include)
conf = configuration_data()
conf.set('LIBKIWIX_VERSION', '"@0@"'.format(meson.project_version()))
if build_machine.system() == 'windows'
extra_link_args = ['-lshlwapi', '-lwinmm']
else
extra_link_args = []
endif
subdir('include')
subdir('scripts')
subdir('static')
@@ -73,17 +102,10 @@ if get_option('doc')
subdir('docs')
endif
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl', 'libmicrohttpd', 'xapian-core']
pkg_conf = configuration_data()
pkg_conf.set('prefix', get_option('prefix'))
pkg_conf.set('requires', ' '.join(pkg_requires))
pkg_conf.set('extra_libs', ' '.join(extra_libs))
pkg_conf.set('extra_cflags', extra_cflags)
pkg_conf.set('version', meson.project_version())
configure_file(output : 'kiwix.pc',
configuration : pkg_conf,
input : 'kiwix.pc.in',
install_dir: get_option('libdir')+'/pkgconfig'
)
pkg_mod = import('pkgconfig')
pkg_mod.generate(libraries : [libkiwix] + extra_libs,
version : meson.project_version(),
name : 'libkiwix',
filebase : 'libkiwix',
description : 'A library that contains useful primitives that Kiwix readers have in common',
extra_cflags: extra_cflags)

View File

@@ -61,7 +61,7 @@ lang_table_entry_cxx_template = '''
cxxfile_template = '''// This file is automatically generated. Do not modify it.
#include "server/i18n.h"
#include "server/i18n_utils.h"
namespace kiwix {
namespace i18n {

View File

@@ -61,6 +61,32 @@ resource_decl_template = """{namespaces_open}
extern const std::string {identifier};
{namespaces_close}"""
BINARY_RESOURCE_EXTENSIONS = {'.ico', '.png', '.ttf'}
TEXT_RESOURCE_EXTENSIONS = {
'.css',
'.html',
'.js',
'.json',
'.svg',
'.tmpl',
'.webmanifest',
'.xml',
}
if not BINARY_RESOURCE_EXTENSIONS.isdisjoint(TEXT_RESOURCE_EXTENSIONS):
raise RuntimeError(f"The following file type extensions are declared to be both binary and text: {BINARY_RESOURCE_EXTENSIONS.intersection(TEXT_RESOURCE_EXTENSIONS)}")
def is_binary_resource(filename):
_, extension = os.path.splitext(filename)
is_binary = extension in BINARY_RESOURCE_EXTENSIONS
is_text = extension in TEXT_RESOURCE_EXTENSIONS
if not is_binary and not is_text:
# all file type extensions of static resources must be listed
# in either BINARY_RESOURCE_EXTENSIONS or TEXT_RESOURCE_EXTENSIONS
raise RuntimeError(f"Unknown file type extension: {extension}")
return is_binary
class Resource:
def __init__(self, base_dirs, filename, cacheid=None):
filename = filename
@@ -72,6 +98,8 @@ class Resource:
try:
with open(os.path.join(base_dir, filename), 'rb') as f:
self.data = f.read()
if not is_binary_resource(filename):
self.data = self.data.replace(b"\r\n", b"\n")
found = True
break
except FileNotFoundError:

View File

@@ -4,6 +4,7 @@
#include "xmlrpc.h"
#include <iostream>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <thread>
#include <chrono>
@@ -29,18 +30,41 @@
namespace kiwix {
Aria2::Aria2():
namespace {
void pauseAnyActiveDownloads(const std::string& ariaSessionFilePath)
{
std::ifstream inputFile(ariaSessionFilePath);
if ( !inputFile )
return;
std::ostringstream ss;
std::string line;
while ( std::getline(inputFile, line) ) {
if ( !startsWith(line, " pause=") ) {
ss << line << "\n";
}
if ( !line.empty() && line[0] != ' ' && line[0] != '#' ) {
ss << " pause=true\n";
}
}
std::ofstream outputFile(ariaSessionFilePath);
outputFile << ss.str();
}
} // unnamed namespace
Aria2::Aria2(std::string sessionFileDir):
mp_aria(nullptr),
m_port(42042),
m_secret(getNewRpcSecret())
{
m_downloadDir = getDataDirectory();
makeDirectory(m_downloadDir);
std::vector<const char*> callCmd;
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
std::string download_dir = "--dir=" + getDataDirectory();
std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session");
std::string session_file = appendToDirectory(sessionFileDir, "kiwix.session");
pauseAnyActiveDownloads(session_file);
std::string session = "--save-session=" + session_file;
std::string inputFile = "--input-file=" + session_file;
// std::string log_dir = "--log=\"" + logDir + "\"";
@@ -67,7 +91,6 @@ Aria2::Aria2():
callCmd.push_back("--enable-rpc");
callCmd.push_back(rpc_secret.c_str());
callCmd.push_back(rpc_port.c_str());
callCmd.push_back(download_dir.c_str());
if (fileReadable(session_file)) {
callCmd.push_back(inputFile.c_str());
}
@@ -97,20 +120,30 @@ Aria2::Aria2():
curl_easy_setopt(p_curl, CURLOPT_PORT, m_port);
curl_easy_setopt(p_curl, CURLOPT_POST, 1L);
curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
curl_easy_setopt(p_curl, CURLOPT_TIMEOUT_MS, 100);
int watchdog = 50;
while(--watchdog) {
typedef std::chrono::duration<double> Seconds;
const double MAX_WAITING_TIME_SECONDS = 1;
const auto t0 = std::chrono::steady_clock::now();
bool maxWaitingTimeWasExceeded = false;
CURLcode res = CURLE_OK;
while ( !maxWaitingTimeWasExceeded ) {
sleep(10);
curlErrorBuffer[0] = 0;
auto res = curl_easy_perform(p_curl);
res = curl_easy_perform(p_curl);
if (res == CURLE_OK) {
break;
} else if (watchdog == 1) {
LOG_ARIA_ERROR();
}
const auto dt = std::chrono::steady_clock::now() - t0;
const double elapsedTime = std::chrono::duration_cast<Seconds>(dt).count();
maxWaitingTimeWasExceeded = elapsedTime > MAX_WAITING_TIME_SECONDS;
}
curl_easy_cleanup(p_curl);
if (!watchdog) {
if ( maxWaitingTimeWasExceeded ) {
LOG_ARIA_ERROR();
throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd);
}
}

View File

@@ -22,11 +22,10 @@ class Aria2
std::unique_ptr<Subprocess> mp_aria;
int m_port;
std::string m_secret;
std::string m_downloadDir;
std::string doRequest(const MethodCall& methodCall);
public:
Aria2();
explicit Aria2(std::string sessionFileDir);
virtual ~Aria2() = default;
void close();

View File

@@ -18,6 +18,7 @@
*/
#include "downloader.h"
#include "tools.h"
#include "tools/pathTools.h"
#include "tools/stringTools.h"
@@ -124,8 +125,8 @@ void Download::cancelDownload()
}
/* Constructor */
Downloader::Downloader() :
mp_aria(new Aria2())
Downloader::Downloader(std::string sessionFileDir) :
mp_aria(new Aria2(sessionFileDir))
{
try {
for (auto gid : mp_aria->tellWaiting()) {
@@ -150,11 +151,20 @@ Downloader::Downloader() :
/* Destructor */
Downloader::~Downloader()
{
close();
}
void Downloader::close()
{
mp_aria->close();
if ( mp_aria ) {
try {
mp_aria->close();
} catch (const std::exception& err) {
std::cerr << "ERROR: Failed to save the downloader state: "
<< err.what() << std::endl;
}
mp_aria.reset();
}
}
std::vector<std::string> Downloader::getDownloadIds() const {
@@ -166,13 +176,49 @@ std::vector<std::string> Downloader::getDownloadIds() const {
return ret;
}
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options)
namespace
{
bool downloadCanBeReused(const Download& d,
const std::string& uri,
const Downloader::Options& /*options*/)
{
const auto& uris = d.getUris();
const bool sameURI = std::find(uris.begin(), uris.end(), uri) != uris.end();
if ( !sameURI )
return false;
switch ( d.getStatus() ) {
case Download::K_ERROR:
case Download::K_UNKNOWN:
case Download::K_REMOVED:
return false;
case Download::K_ACTIVE:
case Download::K_WAITING:
case Download::K_PAUSED:
return true; // XXX: what if options are different?
case Download::K_COMPLETE:
return fileExists(d.getPath()); // XXX: what if options are different?
}
return false;
}
} // unnamed namespace
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::string& downloadDir, Options options)
{
std::unique_lock<std::mutex> lock(m_lock);
options.erase(std::remove_if(options.begin(), options.end(), [](const auto& option) {
return option.first == "dir";
}), options.end());
options.push_back({"dir", downloadDir});
for (auto& p: m_knownDownloads) {
auto& d = p.second;
auto& uris = d->getUris();
if (std::find(uris.begin(), uris.end(), uri) != uris.end())
if ( downloadCanBeReused(*d, uri, options) )
return d;
}
std::vector<std::string> uris = {uri};

View File

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

View File

@@ -645,8 +645,6 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
//queryParser.set_stemmer(Xapian::Stem(iso639_3ToXapian(???)));
//queryParser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
const auto flags = Xapian::QueryParser::FLAG_PHRASE
| Xapian::QueryParser::FLAG_BOOLEAN
| Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE
| Xapian::QueryParser::FLAG_LOVEHATE
| Xapian::QueryParser::FLAG_WILDCARD
| partialQueryFlag;

View File

@@ -29,28 +29,32 @@ HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool w
auto& currentBook = library.getBookById(bookId);
auto bookName = currentBook.getHumanReadableIdFromPath();
m_idToName[bookId] = bookName;
m_nameToId[bookName] = bookId;
mapName(library, bookName, bookId);
if (!withAlias)
continue;
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
if (aliasName == bookName) {
continue;
}
if (m_nameToId.find(aliasName) == m_nameToId.end()) {
m_nameToId[aliasName] = bookId;
} else {
auto alreadyPresentPath = library.getBookById(m_nameToId[aliasName]).getPath();
std::cerr << "Path collision: " << alreadyPresentPath
<< " and " << currentBook.getPath()
<< " can't share the same URL path '" << aliasName << "'."
<< " Therefore, only " << alreadyPresentPath
<< " will be served." << std::endl;
if (aliasName != bookName) {
mapName(library, aliasName, bookId);
}
}
}
void HumanReadableNameMapper::mapName(const Library& library, std::string name, std::string bookId) {
if (m_nameToId.find(name) == m_nameToId.end()) {
m_nameToId[name] = bookId;
} else {
const auto& currentBook = library.getBookById(bookId);
auto alreadyPresentPath = library.getBookById(m_nameToId[name]).getPath();
std::cerr << "Path collision: '" << alreadyPresentPath
<< "' and '" << currentBook.getPath()
<< "' can't share the same URL path '" << name << "'."
<< " Therefore, only '" << alreadyPresentPath
<< "' will be served." << std::endl;
}
}
std::string HumanReadableNameMapper::getNameForId(const std::string& id) const {
return m_idToName.at(id);
}

View File

@@ -32,7 +32,7 @@
#include "libkiwix-resources.h"
#include "tools/stringTools.h"
#include "server/i18n.h"
#include "server/i18n_utils.h"
namespace kiwix
{

View File

@@ -51,6 +51,7 @@ bool Server::start() {
m_withTaskbar,
m_withLibraryButton,
m_blockExternalLinks,
m_ipMode,
m_indexTemplateString,
m_ipConnectionLimit));
return mp_server->start();
@@ -74,14 +75,33 @@ void Server::setRoot(const std::string& root)
}
}
int Server::getPort()
void Server::setAddress(const std::string& addr)
{
m_addr.addr.clear();
m_addr.addr6.clear();
if (addr.empty()) return;
if (addr.find(':') != std::string::npos) { // IPv6
m_addr.addr6 = (addr[0] == '[') ? addr.substr(1, addr.length() - 2) : addr; // Remove brackets if any
} else {
m_addr.addr = addr;
}
}
int Server::getPort() const
{
return mp_server->getPort();
}
std::string Server::getAddress()
IpAddress Server::getAddress() const
{
return mp_server->getAddress();
}
IpMode Server::getIpMode() const
{
return mp_server->getIpMode();
}
}

View File

@@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
#include "i18n.h"
#include "i18n_utils.h"
#include "tools/otherTools.h"
@@ -193,4 +193,13 @@ std::string selectMostSuitableLanguage(const UserLangPreferences& prefs)
return bestLangSoFar;
}
std::string translateBookCategory(const std::string& lang, const std::string& category)
{
try {
return getTranslatedString(lang, "book-category." + category);
} catch (...) {
return category;
}
}
} // namespace kiwix

83
src/server/i18n_utils.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright 2022 Veloman Yunkan <veloman.yunkan@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 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifndef KIWIX_SERVER_I18N_UTILS
#define KIWIX_SERVER_I18N_UTILS
#include "i18n.h"
#include <mustache.hpp>
namespace kiwix
{
struct I18nString {
const char* const key;
const char* const value;
};
struct I18nStringTable {
const char* const lang;
const size_t entryCount;
const I18nString* const entries;
const char* get(const std::string& key) const;
};
namespace i18n
{
class GetTranslatedStringWithMsgId
{
typedef kainjow::mustache::basic_data<std::string> MustacheString;
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
public:
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
MsgIdAndTranslation operator()(const std::string& key) const
{
return {key, getTranslatedString(m_lang, key)};
}
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
{
return {key, expandParameterizedString(m_lang, key, params)};
}
private:
const std::string m_lang;
};
} // namespace i18n
struct LangPreference
{
const std::string lang;
const float preference;
};
typedef std::vector<LangPreference> UserLangPreferences;
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
} // namespace kiwix
#endif // KIWIX_SERVER_I18N_UTILS

View File

@@ -54,7 +54,7 @@ extern "C" {
#include "search_renderer.h"
#include "opds_dumper.h"
#include "html_dumper.h"
#include "i18n.h"
#include "i18n_utils.h"
#include <zim/uuid.h>
#include <zim/error.h>
@@ -85,6 +85,20 @@ namespace kiwix {
namespace
{
bool ipAvailable(const std::string addr)
{
auto interfaces = kiwix::getNetworkInterfacesIPv4Or6();
for (const auto& kv : interfaces) {
const auto& interfaceIps = kv.second;
if ((interfaceIps.addr == addr) || (interfaceIps.addr6 == addr)) {
return true;
}
}
return false;
}
inline std::string normalizeRootUrl(std::string rootUrl)
{
while ( !rootUrl.empty() && rootUrl.back() == '/' )
@@ -407,7 +421,7 @@ public:
InternalServer::InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
std::string addr,
IpAddress addr,
int port,
std::string root,
int nbThreads,
@@ -416,6 +430,7 @@ InternalServer::InternalServer(LibraryPtr library,
bool withTaskbar,
bool withLibraryButton,
bool blockExternalLinks,
IpMode ipMode,
std::string indexTemplateString,
int ipConnectionLimit) :
m_addr(addr),
@@ -428,6 +443,7 @@ InternalServer::InternalServer(LibraryPtr library,
m_withTaskbar(withTaskbar),
m_withLibraryButton(withLibraryButton),
m_blockExternalLinks(blockExternalLinks),
m_ipMode(ipMode),
m_indexTemplateString(indexTemplateString.empty() ? RESOURCE::templates::index_html : indexTemplateString),
m_ipConnectionLimit(ipConnectionLimit),
mp_daemon(nullptr),
@@ -451,28 +467,62 @@ bool InternalServer::start() {
if (m_verbose.load())
flags |= MHD_USE_DEBUG;
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(m_port);
if (m_addr.empty()) {
if (0 != INADDR_ANY) {
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
}
m_addr = kiwix::getBestPublicIp();
struct sockaddr_in sockAddr4={0};
sockAddr4.sin_family = AF_INET;
sockAddr4.sin_port = htons(m_port);
struct sockaddr_in6 sockAddr6={0};
sockAddr6.sin6_family = AF_INET6;
sockAddr6.sin6_port = htons(m_port);
if (m_addr.addr.empty() && m_addr.addr6.empty()) { // No ip address provided
if (m_ipMode == IpMode::AUTO) m_ipMode = IpMode::ALL;
sockAddr6.sin6_addr = in6addr_any;
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
IpAddress bestIps = kiwix::getBestPublicIps();
if (m_ipMode == IpMode::IPV4 || m_ipMode == IpMode::ALL) m_addr.addr = bestIps.addr;
if (m_ipMode == IpMode::IPV6 || m_ipMode == IpMode::ALL) m_addr.addr6 = bestIps.addr6;
} else {
if (inet_pton(AF_INET, m_addr.c_str(), &(sockAddr.sin_addr.s_addr)) == 0) {
std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl;
const std::string addr = !m_addr.addr.empty() ? m_addr.addr : m_addr.addr6;
if (m_ipMode != kiwix::IpMode::AUTO) {
std::cerr << "ERROR: When an IP address is provided the IP mode must not be set" << std::endl;
return false;
}
bool validV4 = inet_pton(AF_INET, m_addr.addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
bool validV6 = inet_pton(AF_INET6, m_addr.addr6.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
if (!validV4 && !validV6) {
std::cerr << "ERROR: invalid IP address: " << addr << std::endl;
return false;
}
if (!ipAvailable(addr)) {
std::cerr << "ERROR: IP address is not available on this system: " << addr << std::endl;
return false;
}
m_ipMode = !m_addr.addr.empty() ? IpMode::IPV4 : IpMode::IPV6;
}
if (m_ipMode == IpMode::ALL) {
flags|=MHD_USE_DUAL_STACK;
} else if (m_ipMode == IpMode::IPV6) {
flags|=MHD_USE_IPv6;
}
struct sockaddr* sockaddr = (m_ipMode==IpMode::ALL || m_ipMode==IpMode::IPV6)
? (struct sockaddr*)&sockAddr6
: (struct sockaddr*)&sockAddr4;
mp_daemon = MHD_start_daemon(flags,
m_port,
NULL,
NULL,
&staticHandlerCallback,
this,
MHD_OPTION_SOCK_ADDR, &sockAddr,
MHD_OPTION_SOCK_ADDR, sockaddr,
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
MHD_OPTION_END);
@@ -725,6 +775,7 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
}
const auto queryString = request.get_optional_param("term", std::string());
const auto mode = request.get_optional_param("mode", std::string());
const auto start = request.get_optional_param<unsigned int>("start", 0);
unsigned int count = request.get_optional_param<unsigned int>("count", 10);
if (count == 0) {
@@ -743,13 +794,17 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
);
const auto lock(searcher->getLock());
auto search = searcher->suggest(queryString);
auto srs = search.getResults(start, count);
for(auto& suggestion: srs) {
results.add(suggestion);
if ( start == 0 && mode == "smart") {
for(const auto& suggestion: search.getSmartSuggestions(count)) {
results.add(suggestion);
}
} else {
for(const auto& suggestion: search.getResults(start, count)) {
results.add(suggestion);
}
}
/* Propose the fulltext search if possible */
if (archive->hasFulltextIndex()) {
results.addFTSearchSuggestion(request.get_user_language(), queryString);
@@ -937,7 +992,7 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
} catch(std::runtime_error& e) {
// Searcher->search will throw a runtime error if there is no valid xapian database to do the search.
// (in case of zim file not containing a index)
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css);
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css_tmpl);
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
"fulltext-search-unavailable",
"404-page-heading",
@@ -1035,9 +1090,7 @@ std::unique_ptr<Response> InternalServer::handle_captured_external(const Request
return UrlNotFoundResponse(request);
}
auto data = get_default_data();
data.set("source", source);
return ContentResponse::build(RESOURCE::templates::captured_external_html, data, "text/html; charset=utf-8");
return BlockExternalLinkResponse(request, m_root, source);
}
std::unique_ptr<Response> InternalServer::handle_catch(const RequestContext& request)
@@ -1071,15 +1124,6 @@ InternalServer::search_catalog(const RequestContext& request,
namespace
{
ParameterizedMessage suggestSearchMsg(const std::string& searchURL, const std::string& pattern)
{
return ParameterizedMessage("suggest-search",
{
{ "PATTERN", pattern },
{ "SEARCH_URL", searchURL }
});
}
///////////////////////////////////////////////////////////////////////////////
// The content security policy below is set on responses to the /content
// endpoint in order to prevent the ZIM content from interfering with the
@@ -1133,9 +1177,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
} catch (const std::out_of_range& e) {}
if (archive == nullptr) {
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern);
return UrlNotFoundResponse(request)
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
return NewHTTP404Response(request, m_root, m_root + url);
}
const std::string archiveUuid(archive->getUuid());
@@ -1180,9 +1222,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
if (m_verbose.load())
printf("Failed to find %s\n", urlStr.c_str());
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern);
return UrlNotFoundResponse(request)
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
return NewHTTP404Response(request, m_root, m_root + url);
}
}

View File

@@ -27,6 +27,7 @@ extern "C" {
#include "library.h"
#include "name_mapper.h"
#include "tools.h"
#include <zim/search.h>
#include <zim/suggestion.h>
@@ -94,7 +95,7 @@ class InternalServer {
public:
InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
std::string addr,
IpAddress addr,
int port,
std::string root,
int nbThreads,
@@ -103,6 +104,7 @@ class InternalServer {
bool withTaskbar,
bool withLibraryButton,
bool blockExternalLinks,
IpMode ipMode,
std::string indexTemplateString,
int ipConnectionLimit);
virtual ~InternalServer();
@@ -116,8 +118,9 @@ class InternalServer {
void** cont_cls);
bool start();
void stop();
std::string getAddress() { return m_addr; }
int getPort() { return m_port; }
IpAddress getAddress() const { return m_addr; }
int getPort() const { return m_port; }
IpMode getIpMode() const { return m_ipMode; }
private: // functions
std::unique_ptr<Response> handle_request(const RequestContext& request);
@@ -164,7 +167,7 @@ class InternalServer {
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
private: // data
std::string m_addr;
IpAddress m_addr;
int m_port;
std::string m_root; // URI-encoded
std::string m_rootPrefixOfDecodedURL; // URI-decoded
@@ -174,6 +177,7 @@ class InternalServer {
bool m_withTaskbar;
bool m_withLibraryButton;
bool m_blockExternalLinks;
IpMode m_ipMode;
std::string m_indexTemplateString;
int m_ipConnectionLimit;
struct MHD_Daemon* mp_daemon;

View File

@@ -28,7 +28,7 @@
#include <cctype>
#include "tools/stringTools.h"
#include "i18n.h"
#include "i18n_utils.h"
namespace kiwix {

View File

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

View File

@@ -27,7 +27,7 @@
#include <mustache.hpp>
#include "byte_range.h"
#include "etag.h"
#include "i18n.h"
#include "i18n_utils.h"
#include <zim/item.h>
@@ -145,6 +145,13 @@ protected: //data
std::unique_ptr<Data> m_data;
};
struct NewHTTP404Response : ContentResponseBlueprint
{
NewHTTP404Response(const RequestContext& request,
const std::string& root,
const std::string& urlPath);
};
struct HTTPErrorResponse : ContentResponseBlueprint
{
HTTPErrorResponse(const RequestContext& request,
@@ -190,6 +197,13 @@ class ItemResponse : public Response {
std::string m_mimeType;
};
struct BlockExternalLinkResponse : ContentResponseBlueprint
{
BlockExternalLinkResponse(const RequestContext& request,
const std::string& root,
const std::string& externalUrl);
};
}
#endif //KIWIXLIB_SERVER_RESPONSE_H

View File

@@ -10,10 +10,12 @@ 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 = {
{"ami", "Amis"},
{"atj", "atikamekw"},
{"azb", "آذربایجان دیلی"},
{"bcl", "central bikol"},
{"bgs", "tagabawa"},
{"blk", "ပအိုဝ်ႏ"},
{"bxr", "буряад хэлэн"},
{"cbk", "chavacano"},
{"cdo", "閩東語"},
@@ -23,13 +25,16 @@ std::map<std::string, std::string> iso639_3 = {
{"eml", "emiliân-rumagnōl"},
{"fbs", "српскохрватски"},
{"fon", "fɔ̀ngbè"},
{"gcr", "Kriyòl gwiyannen"},
{"guw", "Gungbe"},
{"hbs", "srpskohrvatski"},
{"hyw", "հայերէն/հայերեն"},
{"ido", "ido"},
{"kbp", "kabɩ"},
{"kld", "Gamilaraay"},
{"lbe", "лакку маз"},
{"lbj", "ལ་དྭགས་སྐད་"},
{"lld", "ladin"},
{"map", "Austronesian"},
{"mhr", "марий йылме"},
{"mnw", "ဘာသာမန်"},
@@ -41,10 +46,15 @@ std::map<std::string, std::string> iso639_3 = {
{"olo", "livvi"},
{"pih", "Pitcairn-Norfolk"},
{"pnb", "Western Panjabi"},
{"pwn", "Pinayuanan"},
{"rmr", "Caló"},
{"rmy", "romani shib"},
{"roa", "romance languages"},
{"twi", "twi"},
{"skr", "سرائیکی"},
{"szy", "Sakizaya"},
{"tay", "Tayal"},
{"tgl", "Wikang Tagalog"},
{"twi", "Akwapem 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"},
@@ -58,6 +68,7 @@ void fillLanguagesMap()
const kiwix::ICULanguageInfo lang(*icuLangPtr);
iso639_3.insert({lang.iso3Code(), lang.selfName()});
}
iso639_3.erase("mul");
}
} // unnamed namespace

View File

@@ -19,6 +19,7 @@
*/
#include "tools.h"
#include "stringTools.h"
#include <tools/networkTools.h>
#include <stdio.h>
@@ -32,13 +33,16 @@
#include <stdexcept>
#ifdef _WIN32
#include <iphlpapi.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#else
#include <unistd.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netdb.h>
#endif
@@ -47,6 +51,12 @@
#include <sys/sockio.h>
#endif
namespace kiwix
{
namespace
{
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
{
auto str = static_cast<std::stringstream*>(userdata);
@@ -54,7 +64,15 @@ size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdat
return nmemb;
}
std::string kiwix::download(const std::string& url) {
void updatePublicIpAddress(IpAddress& publicIpAddr, const IpAddress& interfaceIpAddr)
{
if (publicIpAddr.addr.empty()) publicIpAddr.addr = interfaceIpAddr.addr;
if (publicIpAddr.addr6.empty()) publicIpAddr.addr6 = interfaceIpAddr.addr6;
}
} // unnamed namespace
std::string download(const std::string& url) {
auto curl = curl_easy_init();
std::stringstream ss;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
@@ -75,103 +93,161 @@ std::string kiwix::download(const std::string& url) {
return ss.str();
}
std::map<std::string, std::string> kiwix::getNetworkInterfaces() {
std::map<std::string, std::string> interfaces;
namespace
{
#ifdef _WIN32
SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
if (sd == INVALID_SOCKET) {
std::cerr << "Failed to get a socket. Error " << WSAGetLastError() << std::endl;
return interfaces;
}
INTERFACE_INFO InterfaceList[20];
unsigned long nBytesReturned;
if (WSAIoctl(sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList,
sizeof(InterfaceList), &nBytesReturned, 0, 0) == SOCKET_ERROR) {
std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError() << std::endl;
return interfaces;
}
std::map<std::string, IpAddress> getNetworkInterfacesWin() {
std::map<std::string, IpAddress> interfaces;
int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO);
for (int i = 0; i < nNumInterfaces; ++i) {
sockaddr_in *pAddress;
pAddress = (sockaddr_in *) & (InterfaceList[i].iiAddress.AddressIn);
if(pAddress->sin_family == AF_INET) {
/* Add to the map */
std::string interfaceName = std::string(inet_ntoa(pAddress->sin_addr));
interfaces[interfaceName] = interfaceName;
}
}
#else
/* Get Network interfaces information */
char buf[16384];
struct ifconf ifconf;
int fd = socket(PF_INET, SOCK_DGRAM, 0); /* Only IPV4 */
ifconf.ifc_len = sizeof(buf);
ifconf.ifc_buf=buf;
if(ioctl(fd, SIOCGIFCONF, &ifconf)!=0) {
perror("ioctl(SIOCGIFCONF)");
}
const int working_buffer_size = 15000;
const int max_tries = 3;
/* Go through each interface */
struct ifreq *ifreq;
ifreq = ifconf.ifc_req;
for (int i = 0; i < ifconf.ifc_len; ) {
if (ifreq->ifr_addr.sa_family == AF_INET) {
/* Get the network interface ip */
char host[128] = { 0 };
const int error = getnameinfo(&(ifreq->ifr_addr), sizeof(ifreq->ifr_addr),
host, sizeof(host),
0, 0, NI_NUMERICHOST);
if (!error) {
std::string interfaceName = std::string(ifreq->ifr_name);
std::string interfaceIp = std::string(host);
/* Add to the map */
interfaces[interfaceName] = interfaceIp;
} else {
perror("getnameinfo()");
}
ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
// default to unspecified address family (both)
ULONG family = AF_UNSPEC;
ULONG outBufLen = working_buffer_size;
ULONG Iterations = 0;
DWORD dwRetVal = 0;
PIP_ADAPTER_ADDRESSES interfacesHead = NULL;
// Successively allocate the required memory until GetAdaptersAddresses does not
// results in ERROR_BUFFER_OVERFLOW for a maximum of max_tries
do{
interfacesHead = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen);
if (interfacesHead == NULL) {
std::cerr << "Memory allocation failed for IP_ADAPTER_ADDRESSES struct" << std::endl;
return interfaces;
}
/* some systems have ifr_addr.sa_len and adjust the length that
* way, but not mine. weird */
size_t len;
#ifndef __linux__
len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
#else
len = sizeof(*ifreq);
#endif
ifreq = (struct ifreq*)((char*)ifreq+len);
i += len;
dwRetVal = GetAdaptersAddresses(family, flags, NULL, interfacesHead, &outBufLen);
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < max_tries));
if (dwRetVal == NO_ERROR) {
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
unsigned int i = 0;
for (PIP_ADAPTER_ADDRESSES temp = interfacesHead; temp != NULL;
temp = temp->Next) {
pUnicast = temp->FirstUnicastAddress;
if (pUnicast != NULL) {
for (i = 0; pUnicast != NULL; i++){
if (pUnicast->Address.lpSockaddr->sa_family == AF_INET)
{
sockaddr_in *si = (sockaddr_in *)(pUnicast->Address.lpSockaddr);
char host[INET_ADDRSTRLEN]={0};
inet_ntop(AF_INET, &(si->sin_addr), host, sizeof(host));
interfaces[temp->AdapterName].addr=host;
}
else if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6)
{
sockaddr_in6 *si = (sockaddr_in6 *)(pUnicast->Address.lpSockaddr);
char host[INET6_ADDRSTRLEN]={0};
inet_ntop(AF_INET6, &(si->sin6_addr), host, sizeof(host));
if (!IN6_IS_ADDR_LINKLOCAL(&(si->sin6_addr)))
interfaces[temp->AdapterName].addr6=host;
}
pUnicast = pUnicast->Next;
}
}
}
} else {
std::cerr << "Call to GetAdaptersAddresses failed with error: "<< dwRetVal << std::endl;
}
#endif
if (interfacesHead) free(interfacesHead);
return interfaces;
}
std::string kiwix::getBestPublicIp() {
auto interfaces = getNetworkInterfaces();
#else
std::map<std::string, IpAddress> getNetworkInterfacesPosix() {
std::map<std::string, IpAddress> interfaces;
struct ifaddrs *interfacesHead;
if (getifaddrs(&interfacesHead) == -1) {
perror("getifaddrs");
}
for (ifaddrs *temp = interfacesHead; temp != NULL; temp = temp->ifa_next) {
if (temp->ifa_addr == NULL) continue;
if (temp->ifa_addr->sa_family == AF_INET) {
sockaddr_in *si = (sockaddr_in *)(temp->ifa_addr);
char host[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET, &(si->sin_addr), host, sizeof(host));
interfaces[temp->ifa_name].addr=host;
} else if (temp->ifa_addr->sa_family == AF_INET6) {
sockaddr_in6 *si = (sockaddr_in6 *)(temp->ifa_addr);
char host[INET6_ADDRSTRLEN] = {0};
inet_ntop(AF_INET6, &(si->sin6_addr), host, sizeof(host));
if (!IN6_IS_ADDR_LINKLOCAL(&(si->sin6_addr)))
interfaces[temp->ifa_name].addr6=host;
}
}
freeifaddrs(interfacesHead);
return interfaces;
}
#endif
} // unnamed namespace
std::map<std::string, IpAddress> getNetworkInterfacesIPv4Or6() {
#ifdef _WIN32
return getNetworkInterfacesWin();
#else
return getNetworkInterfacesPosix();
#endif
}
std::map<std::string, std::string> getNetworkInterfaces() {
std::map<std::string, std::string> result;
for ( const auto& kv : getNetworkInterfacesIPv4Or6() ) {
const std::string& interfaceName = kv.first;
const auto& ipAddresses = kv.second;
if ( !ipAddresses.addr.empty() ) {
result[interfaceName] = ipAddresses.addr;
}
}
return result;
}
IpAddress getBestPublicIps() {
IpAddress bestPublicIps;
std::map<std::string, IpAddress> interfaces = getNetworkInterfacesIPv4Or6();
#ifndef _WIN32
const char* const prioritizedNames[] =
{ "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
for(auto name: prioritizedNames) {
auto it = interfaces.find(name);
if(it != interfaces.end()) {
return it->second;
const char* const prioritizedNames[] = { "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
for (const auto& name : prioritizedNames) {
const auto it = interfaces.find(name);
if (it != interfaces.end()) {
updatePublicIpAddress(bestPublicIps, it->second);
}
}
#endif
const char* const prefixes[] = { "192.168", "172.16.", "10.0" };
for(auto prefix : prefixes){
for(auto& itr : interfaces) {
auto interfaceIp = itr.second;
if (interfaceIp.find(prefix) == 0) {
return interfaceIp;
const char* const v4prefixes[] = { "192.168", "172.16", "10.0" };
for (const auto& prefix : v4prefixes) {
for (const auto& kv : interfaces) {
const auto& interfaceIps = kv.second;
if (kiwix::startsWith(interfaceIps.addr, prefix)) {
updatePublicIpAddress(bestPublicIps, interfaceIps);
}
}
}
return "127.0.0.1";
updatePublicIpAddress(bestPublicIps, {"127.0.0.1", "::1"});
return bestPublicIps;
}
std::string getBestPublicIp()
{
return getBestPublicIps().addr;
}
} // namespace kiwix

View File

@@ -32,7 +32,7 @@
#endif
#include "tools/stringTools.h"
#include "server/i18n.h"
#include "server/i18n_utils.h"
#include "libkiwix-resources.h"
#include <map>
@@ -384,9 +384,13 @@ void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
: suggestion.getTitle();
result.set("label", escapeForJSON(label, DONT_ESCAPE_QUOTE));
result.set("value", escapeForJSON(suggestion.getTitle(), DONT_ESCAPE_QUOTE));
result.set("kind", "path");
result.set("path", escapeForJSON(suggestion.getPath(), DONT_ESCAPE_QUOTE));
if ( suggestion.getPath().empty() ) {
result.set("kind", "modifiedquery");
} else {
result.set("kind", "path");
result.set("value", escapeForJSON(suggestion.getTitle(), DONT_ESCAPE_QUOTE));
result.set("path", escapeForJSON(suggestion.getPath(), DONT_ESCAPE_QUOTE));
}
result.set("first", m_data.is_empty_list());
m_data.push_back(result);
}

View File

@@ -320,16 +320,6 @@ bool kiwix::fileReadable(const std::string& path)
#endif
}
bool makeDirectory(const std::string& path)
{
#ifdef _WIN32
int status = _wmkdir(Utf8ToWide(path).c_str());
#else
int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
return status == 0;
}
std::string makeTmpDirectory()
{
#ifdef _WIN32
@@ -438,52 +428,6 @@ std::string kiwix::getCurrentDirectory()
return ret;
}
std::string kiwix::getDataDirectory()
{
// Try to get the dataDir from the `KIWIX_DATA_DIR` env var
#ifdef _WIN32
wchar_t* cDataDir = ::_wgetenv(L"KIWIX_DATA_DIR");
if (cDataDir != nullptr) {
return WideToUtf8(cDataDir);
}
#else
char* cDataDir = ::getenv("KIWIX_DATA_DIR");
if (cDataDir != nullptr) {
return cDataDir;
}
#endif
// Compute the dataDir from the user directory.
std::string dataDir;
#ifdef _WIN32
cDataDir = ::_wgetenv(L"APPDATA");
if (cDataDir == nullptr)
cDataDir = ::_wgetenv(L"USERPROFILE");
if (cDataDir != nullptr)
dataDir = WideToUtf8(cDataDir);
#else
cDataDir = ::getenv("XDG_DATA_HOME");
if (cDataDir != nullptr) {
dataDir = cDataDir;
} else {
cDataDir = ::getenv("HOME");
if (cDataDir != nullptr) {
dataDir = cDataDir;
dataDir = appendToDirectory(dataDir, ".local");
dataDir = appendToDirectory(dataDir, "share");
}
}
#endif
if (!dataDir.empty()) {
dataDir = appendToDirectory(dataDir, "kiwix");
makeDirectory(dataDir);
return dataDir;
}
// Let's use the currentDirectory
return getCurrentDirectory();
}
static std::map<std::string, std::string> extMimeTypes = {
{ "html", "text/html"},
{ "htm", "text/html"},

View File

@@ -29,7 +29,6 @@ std::wstring Utf8ToWide(const std::string& str);
unsigned int getFileSize(const std::string& path);
std::string getFileSizeAsString(const std::string& path);
bool makeDirectory(const std::string& path);
std::string makeTmpDirectory();
bool copyFile(const std::string& sourcePath, const std::string& destPath);
bool writeTextFile(const std::string& path, const std::string& content);

View File

@@ -32,6 +32,7 @@
#include <iostream>
#include <iomanip>
#include <regex>
/* tell ICU where to find its dat file (tables) */
void kiwix::loadICUExternalTables()
@@ -256,7 +257,7 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
// If there aren't enough characters left for this to be a
// valid escape code, just use the character and move on
if (it > value.end() - 3) {
if (value.end() - it < 3) {
os << *it;
continue;
}
@@ -439,3 +440,13 @@ template<>
std::string kiwix::extractFromString(const std::string& str) {
return str;
}
std::string kiwix::getSlugifiedFileName(const std::string& filename)
{
#ifdef _WIN32
const std::regex reservedCharsReg(R"([<>:"/\\|?*])");
#else
const std::regex reservedCharsReg("/");
#endif
return std::regex_replace(filename, reservedCharsReg, "_");
}

View File

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

View File

@@ -15,6 +15,7 @@ skin/i18n/he.json
skin/i18n/hi.json
skin/i18n/hy.json
skin/i18n/ia.json
skin/i18n/id.json
skin/i18n/ig.json
skin/i18n/it.json
skin/i18n/ja.json
@@ -23,10 +24,14 @@ skin/i18n/ku-latn.json
skin/i18n/lb.json
skin/i18n/mk.json
skin/i18n/ms.json
skin/i18n/nb.json
skin/i18n/nl.json
skin/i18n/nqo.json
skin/i18n/or.json
skin/i18n/pl.json
skin/i18n/pt-br.json
skin/i18n/pt.json
skin/i18n/ro.json
skin/i18n/ru.json
skin/i18n/sc.json
skin/i18n/sk.json
@@ -34,6 +39,7 @@ skin/i18n/skr-arab.json
skin/i18n/sl.json
skin/i18n/sq.json
skin/i18n/sv.json
skin/i18n/sw.json
skin/i18n/te.json
skin/i18n/test.json
skin/i18n/tr.json

View File

@@ -1,18 +1,23 @@
skin/caret.png
skin/bittorrent.png
skin/magnet.png
skin/404.svg
skin/blocklink.svg
skin/feed.svg
skin/langSelector.svg
skin/download.png
skin/download-white.svg
skin/hash.png
skin/search-icon.svg
skin/iso6391To3.js
skin/isotope.pkgd.min.js
skin/index.js
skin/autoComplete/autoComplete.min.js
skin/error.css
skin/kiwix.css
skin/taskbar.css
skin/index.css
skin/fonts/DMSans-Regular.ttf
skin/fonts/Poppins.ttf
skin/fonts/Roboto.ttf
skin/search_results.css
@@ -37,10 +42,11 @@ templates/catalog_v2_entry.xml
templates/catalog_v2_partial_entry.xml
templates/catalog_v2_categories.xml
templates/catalog_v2_languages.xml
templates/url_of_search_results_css
templates/url_of_search_results_css.tmpl
templates/viewer_settings.js
templates/no_js_library_page.html
templates/no_js_download.html
templates/sexy404.html
opensearchdescription.xml
ft_opensearchdescription.xml
catalog_v2_searchdescription.xml

1
static/skin/404.svg Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

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

View File

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

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,6 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <g id="Interface / Download"> <path id="Vector" d="M6 21H18M12 3V17M12 17L17 12M12 17L7 12" stroke="#f6f5f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g> </g>
</svg>

After

Width:  |  Height:  |  Size: 546 B

159
static/skin/error.css Normal file
View File

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

View File

Binary file not shown.

View File

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

View File

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

View File

@@ -16,5 +16,15 @@
"search": "অনুসন্ধান",
"welcome-to-kiwix-server": "কিউইক্স সার্ভারে স্বাগতম",
"download-links-title": "বই ডাউনলোড করুন",
"preview-book": "প্রাকদর্শন"
"preview-book": "প্রাকদর্শন",
"book-category.wikibooks": "উইকিবই",
"book-category.wikinews": "উইকিসংবাদ",
"book-category.wikipedia": "উইকিপিডিয়া",
"book-category.wikiquote": "উইকিউক্তি",
"book-category.wikisource": "উইকিসংকলন",
"book-category.wikispecies": "উইকিপ্রজাতি",
"book-category.wikiversity": "উইকিবিশ্ববিদ্যালয়",
"book-category.wikivoyage": "উইকিভ্রমণ",
"book-category.wiktionary": "উইকিঅভিধান",
"book-category.other": "অন্যান্য"
}

View File

@@ -42,14 +42,14 @@
"book-filtering-all-categories": "Pubu zaa",
"book-filtering-all-languages": "Bala zaa",
"count-of-matching-books": "{{COUNT}} Buku(nima)",
"download": "Yihibu",
"download": "Deebu",
"direct-download-link-text": "Tibi",
"direct-download-alt-text": "Tibi deebu",
"hash-download-link-text": "Sha256 hash",
"hash-download-alt-text": "Deebu daliŋ",
"welcome-to-kiwix-server": "Maraba Kiwix tum tumda",
"download-links-heading": "Deemi soli zaŋ n-ti <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Yaa mi buku",
"preview-book": "Labi lihi",
"download-links-title": "Deemi buku",
"preview-book": "Daŋyuli",
"unknown-error": "Chiriŋ din bi tooi baŋ"
}

View File

@@ -1,7 +1,9 @@
{
"@metadata": {
"authors": [
"AnnaTheProgrammer777",
"IMayBeABitShy",
"Justman10000",
"Lucas Werkmeister",
"Rofiatmustapha12",
"ThisCarthing"
@@ -47,20 +49,30 @@
"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",
"direct-download-alt-text": "Direktes Herunterladen über HTTP(S)",
"hash-download-link-text": "SHA-256 Prüfsumme",
"hash-download-alt-text": "SHA-256-Dateiprüfsumme anzeigen",
"magnet-link-text": "Magnet Link",
"magnet-alt-text": "Magnet Link herunterladen",
"torrent-download-link-text": "Torrent-Datei",
"torrent-download-alt-text": "Torrent herunterladen",
"magnet-alt-text": "Download über Magnet-Link",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Herunterladen über BitTorrent",
"library-opds-feed-all-entries": "ODPS Feed der Bibliothek - Alle Einträge",
"filter-by-tag": "Nach Tag \"{{TAG}}\" filtern",
"stop-filtering-by-tag": "Filterung nach Tag \"{{TAG}}\" aufheben",
"filter-by-tag": "Nach Tag \"{{{TAG}}}\" filtern",
"stop-filtering-by-tag": "Filterung nach Tag \"{{{TAG}}}\" aufheben",
"library-opds-feed-parameterised": "ODPS Feed der Bibliothek - Einträge mit {{#LANG}\nSprache {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}}{{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Wilkommen beim Kiwix Server",
"download-links-heading": "Download Links für <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Buch herunterladen",
"preview-book": "Vorschau",
"unknown-error": "Unbekannter Fehler"
"unknown-error": "Unbekannter Fehler",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionary",
"book-category.other": "Andere"
}

View File

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

View File

@@ -2,9 +2,12 @@
"@metadata": {
"authors": [
"Kelson",
"Norhorn"
"Norhorn",
"Ανώνυμος Βικιπαιδιστής"
]
},
"name": "Αγγλικά",
"no-such-book": "Δεν υπάρχει τέτοιο βιβλίο: {{BOOK_NAME}}",
"welcome-page-overzealous-filter": "Κανένα αποτέλεσμα. Θέλετε να <a href=\"{{URL}}\">επαναφέρετε το φίλτρο</a>;",
"powered-by-kiwix-html": "Με την υποστήριξη by&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Αναζήτηση",
@@ -15,8 +18,14 @@
"direct-download-link-text": "Απευθείας",
"direct-download-alt-text": "άμεση λήψη",
"hash-download-alt-text": "λήψη αναγνωριστικού",
"magnet-alt-text": "λήψη μαγνήτη",
"torrent-download-link-text": "Αρχείο torrent",
"torrent-download-alt-text": "λήψη torrent",
"filter-by-tag": "Φίλτρο ανά ετικέτα \"{{TAG}}\"",
"stop-filtering-by-tag": "Διακοπή φίλτρου ανά ετικέτα \"{{TAG}}\""
"stop-filtering-by-tag": "Διακοπή φίλτρου ανά ετικέτα \"{{TAG}}\"",
"welcome-to-kiwix-server": "Καλώς ορίσατε στον διακομιστή Kiwix",
"download-links-heading": "Λήψη συνδέσμων για <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Κατεβάστε το βιβλίο",
"preview-book": "Προεπισκόπηση",
"unknown-error": "Άγνωστο σφάλμα"
}

View File

@@ -20,9 +20,24 @@
, "400-page-heading" : "Invalid request"
, "404-page-title" : "Content not found"
, "404-page-heading" : "Not Found"
, "new-404-page-title" : "Page not found"
, "new-404-page-heading" : "Oops. Page not found."
, "404-img-text": "Not found!"
, "path-was-not-found": "The requested path was not found:"
, "404-advice.p1": "The content you're looking for may still be available, but it might be located at a different place within the ZIM file."
, "404-advice.p2": "Please:"
, "404-advice.p3": "Try using the search function to find the content you want"
, "404-advice.p4": "Look for keywords or titles related to the information you're seeking"
, "404-advice.p5": "This approach should help you locate the desired content, even if the original link isn't working properly."
, "500-page-title" : "Internal Server Error"
, "500-page-heading" : "Internal Server Error"
, "500-page-text": "An internal server error occured. We are sorry about that :/"
, "external-link-detected" : "External Link Detected"
, "caution-warning" : "Caution!"
, "external-link-intro" : "You are about to leave Kiwix's ZIM reader to go online to"
, "external-link-advice.p1": "The link you're trying to access is not part of your offline package and requires an internet connection."
, "external-link-advice.p2": "If you can go online, you can attempt to open the link."
, "external-link-advice.p3": "You can otherwise return to your ZIM's offline content by using your browser's back button."
, "fulltext-search-unavailable" : "Fulltext search unavailable"
, "no-search-results": "The fulltext search engine is not available for this content."
, "search-results-page-title": "Search: {{SEARCH_PATTERN}}"
@@ -43,16 +58,16 @@
, "count-of-matching-books": "{{COUNT}} book(s)"
, "download": "Download"
, "direct-download-link-text": "Direct"
, "direct-download-alt-text": "direct download"
, "hash-download-link-text": "Sha256 hash"
, "hash-download-alt-text": "download hash"
, "direct-download-alt-text": "Download directly via HTTP(S)"
, "hash-download-link-text": "SHA-256 checksum"
, "hash-download-alt-text": "Display SHA-256 file checksum"
, "magnet-link-text": "Magnet link"
, "magnet-alt-text": "download magnet"
, "torrent-download-link-text": "Torrent file"
, "torrent-download-alt-text": "download torrent"
, "magnet-alt-text": "Download via Magnet link"
, "torrent-download-link-text": "BitTorrent"
, "torrent-download-alt-text": "Download via BitTorrent"
, "library-opds-feed-all-entries": "Library OPDS Feed - All entries"
, "filter-by-tag": "Filter by tag \"{{TAG}}\""
, "stop-filtering-by-tag": "Stop filtering by tag \"{{TAG}}\""
, "filter-by-tag": "Filter by tag \"{{{TAG}}}\""
, "stop-filtering-by-tag": "Stop filtering by tag \"{{{TAG}}}\""
, "library-opds-feed-parameterised": "Library OPDS Feed - entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}"
, "welcome-to-kiwix-server": "Welcome to Kiwix Server"
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
@@ -60,4 +75,23 @@
, "preview-book": "Preview"
, "non-translated-text": "{{MSG}}"
, "unknown-error": "Unknown error"
, "book-category.gutenberg": "Gutenberg"
, "book-category.iFixit": "iFixit"
, "book-category.mooc": "MOOC"
, "book-category.phet": "Phet"
, "book-category.stack_exchange": "Stack Exchange"
, "book-category.ted": "Ted"
, "book-category.vikidia": "Vikidia"
, "book-category.wikibooks": "Wikibooks"
, "book-category.wikihow": "wikiHow"
, "book-category.wikinews": "Wikinews"
, "book-category.wikipedia": "Wikipedia"
, "book-category.wikiquote": "Wikiquote"
, "book-category.wikisource": "Wikisource"
, "book-category.wikispecies": "Wikispecies"
, "book-category.wikiversity": "Wikiversity"
, "book-category.wikivoyage": "Wikivoyage"
, "book-category.wiktionary": "Wiktionary"
, "book-category.other": "Other"
, "text-loading-content": "Loading Content"
}

View File

@@ -1,8 +1,10 @@
{
"@metadata": {
"authors": [
"AlexanderFF",
"Fitoschido",
"Ovruni",
"Sinopsistrans",
"SpikeShroom",
"Vis4valentine"
]
@@ -16,6 +18,7 @@
"suggest-search": "Haga una búsqueda de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "¡Ups! Error al elegir un artículo aleatorio :(",
"invalid-raw-data-type": "{{DATATYPE}} no es una solicitud válida de contenido en crudo.",
"invalid-request": "La URL solicitada \"{{{url}}}\" no es una solicitud válida.",
"no-value-for-arg": "No se ha proporcionado ningún valor para el argumento {{ARGUMENT}}",
"no-query": "No se ha proporcionado ninguna consulta.",
"raw-entry-not-found": "No se puede encontrar la entrada {{DATATYPE}} {{ENTRY}}",
@@ -25,8 +28,14 @@
"404-page-heading": "No encontrado",
"500-page-title": "Error interno del servidor",
"500-page-heading": "Error interno del servidor",
"500-page-text": "Se produjo un error interno del servidor. Lo sentimos :/",
"fulltext-search-unavailable": "Búsqueda de texto completo no disponible",
"no-search-results": "El motor de búsqueda de texto completo no está disponible para este contenido.",
"search-results-page-title": "Buscar: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "No se encontraron resultados para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "a partir de {{BOOK_TITLE}}",
"word-count": "{{COUNT}} palabras",
"library-button-text": "Ir a la página de bienvenida",
"home-button-text": "Ir a la página principal de '{{BOOK_TITLE}}'",
"random-page-button-text": "Ir a una página seleccionada al azar",
@@ -40,18 +49,30 @@
"count-of-matching-books": "{{COUNT}} libro(s)",
"download": "Descargar",
"direct-download-link-text": "Directamente",
"direct-download-alt-text": "descarga directa",
"hash-download-link-text": "hash sha256",
"hash-download-alt-text": "descargar hash",
"direct-download-alt-text": "Descargar directamente vía HTTP(S)",
"hash-download-link-text": "Suma de verificación SHA-256",
"hash-download-alt-text": "Mostrar la suma de verificación de archivos SHA-256",
"magnet-link-text": "Enlace magnético",
"magnet-alt-text": "Descargar link magnético",
"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}}\"",
"magnet-alt-text": "Descargar mediante enlace Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Descargar a través de BitTorrent",
"library-opds-feed-all-entries": "Biblioteca OPDS Feed - Todas las entradas",
"filter-by-tag": "Filtrar por etiqueta \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Dejar de filtrar por etiqueta \"{{{TAG}}}\"",
"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"
"preview-book": "Previsualizar",
"unknown-error": "Error desconocido",
"book-category.wikibooks": "Wikilibros",
"book-category.wikinews": "Wikinoticias",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikiespecies",
"book-category.wikiversity": "Wikiversidad",
"book-category.wikivoyage": "Wikiviajes",
"book-category.wiktionary": "Wikcionario",
"book-category.other": "Otros"
}

View File

@@ -8,6 +8,7 @@
},
"name": "suomi",
"suggest-full-text-search": "sisältää '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Kirjaa {{BOOK_NAME}} ei ole olemassa",
"url-not-found": "Pyydettyä URL-osoitetta \"{{url}}\" ei löytynyt tältä palvelimelta.",
"400-page-title": "Virheellinen pyyntö",
"400-page-heading": "Virheellinen pyyntö",
@@ -15,6 +16,7 @@
"404-page-heading": "Ei löytynyt",
"500-page-title": "Sisäinen palvelinvirhe",
"500-page-heading": "Sisäinen palvelinvirhe",
"word-count": "{{COUNT}} sanaa",
"library-button-text": "Siirry tervetulosivulle",
"home-button-text": "Siirry kirjan '{{BOOK_TITLE}}' etusivulle",
"random-page-button-text": "Siirry satunnaiselle sivulle",
@@ -22,9 +24,14 @@
"search": "Hae",
"book-filtering-all-categories": "Kaikki luokat",
"book-filtering-all-languages": "Kaikki kielet",
"count-of-matching-books": "{{COUNT}} kirja(a)",
"download": "Lataa",
"magnet-link-text": "Magnet-linkki",
"magnet-alt-text": "lataa magnet",
"torrent-download-link-text": "Torrent-tiedosto",
"torrent-download-alt-text": "lataa torrent-tiedosto",
"filter-by-tag": "Suodata tunnisteen ”{{TAG}}” mukaan",
"download-links-title": "Lataa kirja",
"preview-book": "Esikatsele"
"preview-book": "Esikatsele",
"unknown-error": "Tuntematon virhe"
}

View File

@@ -2,7 +2,9 @@
"@metadata": {
"authors": [
"Adriendelucca",
"Benoit74",
"Gomoko",
"Goombiis",
"Melimeli",
"Stephane",
"Thibaut120094",
@@ -52,20 +54,31 @@
"count-of-matching-books": "{{COUNT}} livre(s)",
"download": "Télécharger",
"direct-download-link-text": "Direct",
"direct-download-alt-text": "téléchargement direct",
"hash-download-link-text": "Hachage sha256",
"hash-download-alt-text": "télécharger le hachage",
"direct-download-alt-text": "Télécharger directement via HTTP(S)",
"hash-download-link-text": "Hachage SHA-256",
"hash-download-alt-text": "Affiche le hachage SHA-256 du fichier",
"magnet-link-text": "Lien Magnet",
"magnet-alt-text": "télécharger le lien Magnet",
"torrent-download-link-text": "Fichier torrent",
"torrent-download-alt-text": "télécharger le torrent",
"magnet-alt-text": "Télécharger via le lien Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Télécharger via BitTorrent",
"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}} »",
"filter-by-tag": "Filtrer par le tag \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Arrêter de filtrer par le tag \"{{{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",
"unknown-error": "Erreur inconnue"
"unknown-error": "Erreur inconnue",
"book-category.wikibooks": "Wikilivres",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipédia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversité",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionnaire",
"book-category.other": "Autre",
"text-loading-content": "Chargement du contenu"
}

View File

@@ -45,20 +45,32 @@
"count-of-matching-books": "{{COUNT}} ספרים",
"download": "הורדה",
"direct-download-link-text": "ישירה",
"direct-download-alt-text": "הורדה ישירה",
"hash-download-link-text": "גיבוב Sha256",
"hash-download-alt-text": ורדת גיבוב",
"direct-download-alt-text": "הורדה ישירה דרך HTTP(S)",
"hash-download-link-text": "סיכום ביקורת Sha256",
"hash-download-alt-text": צגת סיכום ביקורת SHA-256",
"magnet-link-text": "קישור Magnet",
"magnet-alt-text": "הורדת magnet",
"torrent-download-link-text": "קובץ טורנט",
"torrent-download-alt-text": "הורדת טורנט",
"magnet-alt-text": "הורדה באמצעות קישור magnet",
"torrent-download-link-text": "ביטורנט",
"torrent-download-alt-text": "הורדה באמצעות ביטורנט",
"library-opds-feed-all-entries": "הזנת ספריית OPDS - כל הרשומות",
"filter-by-tag": "סינון לפי התג \"{{TAG}}\"",
"stop-filtering-by-tag": "להפסיק סינון לפי התג \"{{TAG}}\"",
"filter-by-tag": "לסנן לפי התג \"{{{TAG}}}\"",
"stop-filtering-by-tag": "להפסיק סינון לפי התג \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "הזנת ספריית OPDS - רשומות שתואמות ל{{#LANG}}\nשפה: {{LANG}} {{/LANG}}{{#CATEGORY}}\nקטגוריה: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nתג: {{TAG}} {{/TAG}}{{#Q}}\nשאילתה: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "ברוך בואך לשרת קיוויקס",
"download-links-heading": "הורדת קישורים עבור <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "הורדת ספר",
"preview-book": "תצוגה מקדימה",
"unknown-error": "שגיאה בלתי־ידועה"
"unknown-error": "שגיאה בלתי־ידועה",
"book-category.gutenberg": "גוטנברג",
"book-category.vikidia": "ויקידיה",
"book-category.wikibooks": "ויקיספר",
"book-category.wikinews": "ויקיחדשות",
"book-category.wikipedia": "ויקיפדיה",
"book-category.wikiquote": "ויקיציטוט",
"book-category.wikisource": "ויקיטקסט (Wikisource)",
"book-category.wikispecies": "ויקימינים",
"book-category.wikiversity": "ויקיברסיטה",
"book-category.wikivoyage": "ויקימסע",
"book-category.wiktionary": "ויקימילון",
"book-category.other": "אחר"
}

View File

@@ -52,5 +52,15 @@
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
"download-links-title": "पुस्तक डाउनलोड करें",
"preview-book": "पूर्वावलोकन"
"preview-book": "पूर्वावलोकन",
"book-category.wikibooks": "विकिपुस्तक",
"book-category.wikinews": "विकिसमाचार",
"book-category.wikipedia": "विकिपीडिया",
"book-category.wikiquote": "विकिसूक्ति",
"book-category.wikisource": "विकिस्रोत",
"book-category.wikispecies": "विकिप्रजाति",
"book-category.wikiversity": "विकिविश्वविद्यालय",
"book-category.wikivoyage": "विकियात्रा",
"book-category.wiktionary": "विकिकोश",
"book-category.other": "अन्य"
}

View File

@@ -17,5 +17,15 @@
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
"random-page-button-text": "Բացել պատահական էջ",
"searchbox-tooltip": "Որոնել '{{BOOK_TITLE}}'֊ում",
"book-filtering-all-categories": "Բոլոր կատեգորիաներ"
"book-filtering-all-categories": "Բոլոր կատեգորիաներ",
"book-category.wikibooks": "Վիքիգրքեր",
"book-category.wikinews": "Վիքիլուրեր",
"book-category.wikipedia": "Վիքիպեդիա",
"book-category.wikiquote": "Վիքիքաղվածք",
"book-category.wikisource": "Վիքիդարան",
"book-category.wikispecies": "Վիքիցեղեր",
"book-category.wikiversity": "Վիքիլսարան",
"book-category.wikivoyage": "Վիքիճամփորդ",
"book-category.wiktionary": "Վիքիբառարան",
"book-category.other": "Այլ"
}

View File

@@ -13,7 +13,8 @@
"url-not-found": "Le URL reuqestate \"{{url}}\" non ha essite trovate sur iste servitor.",
"suggest-search": "Facer un recerca in texto complete de <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Non poteva eliger un articulo aleatori :(",
"invalid-raw-data-type": "{{DATATYPE}} non es un request valide pro contento crude.",
"invalid-raw-data-type": "{{DATATYPE}} non es un requesta valide pro contento brute.",
"invalid-request": "Le URL requestate “{{{url}}}” non es un requesta valide.",
"no-value-for-arg": "Necun valor fornite pro le argumento {{ARGUMENT}}",
"no-query": "Necun consulta fornite.",
"raw-entry-not-found": "Non pote trovar le entrata {{ENTRY}} del typo {{DATATYPE}}",
@@ -23,8 +24,14 @@
"404-page-heading": "Non trovate",
"500-page-title": "Error interne del servitor",
"500-page-heading": "Error interne del servitor",
"500-page-text": "Un error interne del servitor ha occurrite. Nos lo regretta :/",
"fulltext-search-unavailable": "Le recerca in texto complete es indisponibile",
"no-search-results": "Le motor de recerca in texto complete non es disponibile pro iste contento.",
"search-results-page-title": "Cercar: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultatos <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> pro <b>“{{{SEARCH_PATTERN}}}”</b>",
"empty-search-results-page-header": "Necun resultato ha essite trovate pro <b>“{{{SEARCH_PATTERN}}}”</b>",
"search-result-book-info": "de {{BOOK_TITLE}}",
"word-count": "{{COUNT}} parolas",
"library-button-text": "Ir al pagina de benvenita",
"home-button-text": "Ir al pagina principal de ''{{BOOK_TITLE}}",
"random-page-button-text": "Ir a un pagina seligite aleatorimente",
@@ -38,19 +45,30 @@
"count-of-matching-books": "{{COUNT}} libro(s)",
"download": "Discargar",
"direct-download-link-text": "Directe",
"direct-download-alt-text": "discargamento directe",
"hash-download-link-text": "Hash SHA256",
"hash-download-alt-text": "hash del discargamento",
"direct-download-alt-text": "Discargamento directe per HTTP(S)",
"hash-download-link-text": "Summa de controlo SHA-256",
"hash-download-alt-text": "Monstrar le summa de controlo SHA-256 del file",
"magnet-link-text": "Ligamine Magnet",
"magnet-alt-text": "ligamine \"magnet\" de discargamento",
"torrent-download-link-text": "File Torrent",
"torrent-download-alt-text": "discargar Torrent",
"magnet-alt-text": "Discargar con ligamine Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Discargar per medio de BitTorrent",
"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}}\"",
"filter-by-tag": "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"
"preview-book": "Previsualisation",
"unknown-error": "Error incognite",
"book-category.wikibooks": "Wikilibros",
"book-category.wikinews": "Wikinovas",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversitate",
"book-category.wikivoyage": "Wikiviage",
"book-category.wiktionary": "Wiktionario",
"book-category.other": "Altere"
}

74
static/skin/i18n/id.json Normal file
View File

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

View File

@@ -3,6 +3,7 @@
"authors": [
"Albano",
"Beta16",
"Clorofolle",
"Luca.favorido",
"McDutchie"
]
@@ -21,6 +22,9 @@
"400-page-heading": "Richiesta non valida",
"404-page-title": "Contenuto non trovato",
"404-page-heading": "Non trovato",
"new-404-page-title": "Pagina non trovata",
"new-404-page-heading": "Oops. Pagina non trovata.",
"404-img-text": "Non trovato!",
"500-page-title": "Errore interno del server",
"500-page-heading": "Errore interno del server",
"500-page-text": "Si è verificato un errore interno del server. Ci dispiace :/",
@@ -33,11 +37,31 @@
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
"random-page-button-text": "Vai a una pagina selezionata casualmente",
"searchbox-tooltip": "Cerca '{{BOOK_TITLE}}'",
"welcome-page-overzealous-filter": "Nessun risultato. Vuoi <a href=\"{{URL}}\">reimpostare il filtro</a>?",
"search": "Cerca",
"book-filtering-all-categories": "Tutte le categorie",
"book-filtering-all-languages": "Tutte le lingue",
"count-of-matching-books": "{{COUNT}} libro/i",
"download": "Scarica",
"direct-download-link-text": "Download diretto",
"direct-download-alt-text": "Scarica direttamente tramite HTTP(S)",
"magnet-alt-text": "Scarica tramite collegamento Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Scarica tramite BitTorrent",
"welcome-to-kiwix-server": "Benvenuti al server Kiwix",
"download-links-heading": "Link per scaricare <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Scarica libro",
"preview-book": "Anteprima",
"unknown-error": "Errore sconosciuto"
"unknown-error": "Errore sconosciuto",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinotizie",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversità",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wikizionario",
"book-category.other": "Altro",
"text-loading-content": "Caricamento contenuto"
}

View File

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

View File

@@ -7,14 +7,27 @@
]
},
"name": "Lëtzebuergesch",
"suggest-full-text-search": "enthält '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Buch net fonnt: {{BOOK_NAME}}",
"too-many-books": "Ze vill Bicher ugefrot ({{NB_BOOKS}}), d'Limitt läit bei {{LIMIT}}",
"url-not-found": "Déi ugefroten URL „{{url}}“ gouf op dësem Server net fonnt.",
"suggest-search": "Maacht eng Volltext-Sich fir <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Et konnt keen zoufällegen Artikel ausgewielt ginn :(",
"404-page-title": "Inhalt net fonnt",
"404-page-heading": "Net fonnt",
"new-404-page-title": "Säit net fonnt",
"new-404-page-heading": "Ups. Säit net fonnt.",
"404-img-text": "Net fonnt!",
"500-page-title": "Interne Feeler um Server",
"500-page-heading": "Interne Feeler um Server",
"500-page-text": "Et ass en interne Serverfeeler opgetrueden. Mir entschëllegen eis dofir :/",
"caution-warning": "Opgepasst!",
"fulltext-search-unavailable": "Volltext-Sich net verfügbar",
"search-results-page-title": "Sichen: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultater <b>{{START}}-{{END}}</b> vu(n) <b>{{COUNT}}</b> fir <b>„{{{SEARCH_PATTERN}}}“</b>",
"empty-search-results-page-header": "Keng Resultater fonnt fir <b>„{{{SEARCH_PATTERN}}}“</b>",
"search-result-book-info": "aus {{BOOK_TITLE}}",
"word-count": "{{COUNT}} Wierder",
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
"random-page-button-text": "Gitt op eng zoufälleg gewielte Säit",
"searchbox-tooltip": "No '{{BOOK_TITLE}}' sichen",
@@ -25,6 +38,18 @@
"count-of-matching-books": "{{COUNT}} Buch/Bicher",
"download": "Eroflueden",
"direct-download-link-text": "Direkt",
"torrent-download-link-text": "BitTorrent",
"download-links-title": "Buch eroflueden",
"unknown-error": "Onbekannte Feeler"
"unknown-error": "Onbekannte Feeler",
"book-category.stack_exchange": "Stack Exchange",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionnaire",
"book-category.other": "Anerer"
}

View File

@@ -45,20 +45,39 @@
"count-of-matching-books": "{{COUNT}} книги",
"download": "Преземи",
"direct-download-link-text": "Непосредно",
"direct-download-alt-text": "непосредно преземање",
"hash-download-link-text": "Sha256-тараба",
"hash-download-alt-text": "преземи тараба",
"direct-download-alt-text": "Непосредно преземање преку HTTP(S)",
"hash-download-link-text": "Контролен збир Sha256",
"hash-download-alt-text": "Прикажи контролен збир SHA-256 на податотеката",
"magnet-link-text": "Магнетна врска",
"magnet-alt-text": "преземи магнет",
"torrent-download-link-text": "Торентна податотека",
"torrent-download-alt-text": "преземи торент",
"magnet-alt-text": "Преземи преку Magnet-врска",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Преземи преку BitTorrent",
"library-opds-feed-all-entries": "Библиотечен тековник на OPDS — Сите ставки",
"filter-by-tag": "Филтрирај по ознаката „{{TAG}}“",
"stop-filtering-by-tag": "Запри филтрирање по ознаката „{{TAG}}“",
"filter-by-tag": "Филтрирај по ознаката „{{{TAG}}}“",
"stop-filtering-by-tag": "Запри филтрирање по ознаката „{{{TAG}}}“",
"library-opds-feed-parameterised": "Библиотечен тековник на OPDS — ставки што одговараат на {{#LANG}}\nЈазик: {{LANG}} {{/LANG}}{{#CATEGORY}}\nКатегорија: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nОзнака: {{TAG}} {{/TAG}}{{#Q}}\nБарање: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Добре дојдовте на Опслужувачот на Кивикс",
"download-links-heading": "Врски за преземање на <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Преземи книга",
"preview-book": "Преглед",
"unknown-error": "Непозната грешка"
"unknown-error": "Непозната грешка",
"book-category.gutenberg": "Гутенберг",
"book-category.iFixit": "iFixit",
"book-category.mooc": "MOOC",
"book-category.phet": "Phet",
"book-category.stack_exchange": "Stack Exchange",
"book-category.ted": "Ted",
"book-category.vikidia": "Викидија",
"book-category.wikibooks": "Викикниги",
"book-category.wikihow": "wikiHow",
"book-category.wikinews": "Викивести",
"book-category.wikipedia": "Википедија",
"book-category.wikiquote": "Викицитат",
"book-category.wikisource": "Викиизвор",
"book-category.wikispecies": "Викивидови",
"book-category.wikiversity": "Викиуниверзитет",
"book-category.wikivoyage": "Википатување",
"book-category.wiktionary": "Викиречник",
"book-category.other": "друго",
"text-loading-content": "Ја вчитувам содржината"
}

74
static/skin/i18n/nb.json Normal file
View File

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

View File

@@ -1,8 +1,10 @@
{
"@metadata": {
"authors": [
"ABPMAB",
"Kelson",
"McDutchie",
"Siebrand",
"Vistaus"
]
},
@@ -15,6 +17,7 @@
"suggest-search": "In volledige tekst zoeen naar <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oeps! Kan geen willekeurig artikel kiezen :(",
"invalid-raw-data-type": "{{DATATYPE}} is geen geldig verzoek voor onbewerkte inhoud.",
"invalid-request": "De gevraagde URL “{{{url}}}” is onjuist.",
"no-value-for-arg": "Er is geen waarde opgegeven bij {{ARGUMENT}}",
"no-query": "Er is geen zoekterm opgegeven.",
"raw-entry-not-found": "Kan het {{DATATYPE}}-item {{ENTRY}} niet vinden",
@@ -24,8 +27,14 @@
"404-page-heading": "Niet gevonden",
"500-page-title": "Interne serverfout",
"500-page-heading": "Interne serverfout",
"500-page-text": "Er is een interne serverfout opgetreden. Dat spijt ons",
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
"search-results-page-title": "Zoeken: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultaten <b>{{START}}-{{END}}</b> van <b>{{COUNT}}</b> voor <b>“{{{SEARCH_PATTERN}}}”</b>",
"empty-search-results-page-header": "Er zijn geen resultaten gevonden voor <b>“{{{SEARCH_PATTERN}}}”</b>",
"search-result-book-info": "uit {{BOOK_TITLE}}",
"word-count": "{{COUNT}} woorden",
"library-button-text": "Naar de welkomstpagina",
"home-button-text": "Naar de hoofdpagina van {{BOOK_TITLE}}",
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
@@ -39,19 +48,31 @@
"count-of-matching-books": "{{COUNT}} boek(en)",
"download": "Downloaden",
"direct-download-link-text": "Direct",
"direct-download-alt-text": "directe download",
"hash-download-link-text": "SHA256-hash",
"hash-download-alt-text": "controlesom (hash) van de download",
"direct-download-alt-text": "Direct downloaden via HTTP(S)",
"hash-download-link-text": "SHA-256-controlesom",
"hash-download-alt-text": "De SHA-256-controlesom van het bestand weergeven",
"magnet-link-text": "Magnet-link",
"magnet-alt-text": "magnet-link van de download",
"torrent-download-link-text": "Torrent-bestand",
"torrent-download-alt-text": "torrent downloaden",
"magnet-alt-text": "Downloaden via Magnet-link",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Downloaden via BitTorrent",
"library-opds-feed-all-entries": "OPDS-feed bibliotheek: alle vermeldingen",
"filter-by-tag": "Filteren op label “{{TAG}}”",
"stop-filtering-by-tag": "Niet meer filteren op label “{{TAG}}”",
"filter-by-tag": "Filteren op label “{{{TAG}}}”",
"stop-filtering-by-tag": "Niet meer filteren op label “{{{TAG}}}”",
"library-opds-feed-parameterised": "OPDS-feed bibliotheek: vermeldingen die overeenkomen met {{#LANG}}\nTaal: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nLabel: {{TAG}} {{/TAG}}{{#Q}}\nZoekopdracht: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Boek downloaden",
"preview-book": "Voorvertoning"
"preview-book": "Voorvertoning",
"unknown-error": "Onbekende fout",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinieuws",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "WikiWoordenboek",
"book-category.other": "Overige",
"text-loading-content": "Inhoud laden"
}

View File

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

View File

@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
"Gouri"
"Gouri",
"Psubhashish"
]
},
"name": "ଓଡ଼ିଆ",
@@ -10,7 +11,7 @@
"too-many-books": "ଅତ୍ୟଧିକ ବହି ଅନୁରୋଧ (${{NB_BOOKS}}) ଯେଉଁଠାରେ ସୀମା ${{LIMIT}} |",
"no-book-found": "କୌଣସି ପୁସ୍ତକ ଚଯ଼ନ ମାନଦଣ୍ଡ ସହ ମେଳ ଖାଉନାହିଁ ।",
"url-not-found": "ଅନୁରୋଧ କରାଯାଇଥିବା URL \"{{url}}\" ଏହି ସର୍ଭରରେ ମିଳିଲା ନାହିଁ |",
"suggest-search": "<a href=\"${{{SEARCH_URL}}}\">${{PATTERN}} for</a> ପାଇଁ ଏକ ସମ୍ପୂର୍ଣ୍ଣ ପାଠ ସନ୍ଧାନ କର |",
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> ପାଇଁ ପୂରା ପାଠ ଖୋଜନ୍ତୁ |",
"random-article-failure": "ଓହୋ! ଏକ ଅନିୟମିତ ପ୍ରବନ୍ଧ ବାଛିବାରେ ବିଫଳ :(",
"invalid-raw-data-type": "{{DATATYPE}} କଞ୍ଚା ବିଷୟବସ୍ତୁ ପାଇଁ ଏକ ବ valid ଧ ଅନୁରୋଧ ନୁହେଁ |",
"no-value-for-arg": "ଯୁକ୍ତି ପାଇଁ କୌଣସି ମୂଲ୍ଯ଼ ପ୍ରଦାନ କରାଯାଇନାହିଁ ${{ARGUMENT}}",

View File

@@ -34,5 +34,6 @@
"torrent-download-link-text": "Plik torrent",
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
"download-links-title": "Pobierz książkę",
"preview-book": "Podgląd"
"preview-book": "Podgląd",
"book-category.other": "Inne"
}

View File

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

73
static/skin/i18n/pt.json Normal file
View File

@@ -0,0 +1,73 @@
{
"@metadata": {
"authors": [
"B3rnas"
]
},
"name": "português",
"suggest-full-text-search": "contendo '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Não existe o livro: {{BOOK_NAME}}",
"too-many-books": "Demasiadas solicitações de livros ({{NB_BOOKS}}) onde o limite é {{LIMIT}}",
"no-book-found": "Nenhum livro corresponde aos critérios de seleção",
"url-not-found": "A URL solicitada \"{{url}}\" não foi encontrada neste servidor.",
"suggest-search": "Faça uma pesquisa de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Erro ao eleger um artigo aleatório :(",
"invalid-raw-data-type": "{{DATATYPE}} não é uma solicitação válida para conteúdo bruto.",
"invalid-request": "A URL solicitada \"{{{url}}}\" não é uma solicitação válida.",
"no-value-for-arg": "Nenhum valor fornecido para o argumento {{ARGUMENT}}",
"no-query": "Nenhuma consulta fornecida.",
"raw-entry-not-found": "Não é possível encontrar a entrada {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Solicitação inválida",
"400-page-heading": "Solicitação inválida",
"404-page-title": "Conteúdo não encontrado",
"404-page-heading": "Não encontrado",
"500-page-title": "Erro interno do servidor",
"500-page-heading": "Erro interno do servidor",
"500-page-text": "Ocorreu um erro interno do servidor. Lamentamos por isso :/",
"fulltext-search-unavailable": "Pesquisa de texto completo indisponível",
"no-search-results": "O motor de busca de texto completo não está disponível para este conteúdo.",
"search-results-page-title": "Pesquisar: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Nenhum resultado foi encontrado para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "de {{BOOK_TITLE}}",
"word-count": "{{COUNT}} palavras",
"library-button-text": "Ir para página inicial",
"home-button-text": "Vá para a página principal de '{{BOOK_TITLE}}'",
"random-page-button-text": "Vá para uma página selecionada aleatoriamente",
"searchbox-tooltip": "Procurar '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Dois ou mais livros em idiomas diferentes participariam da pesquisa, o que poderia levar a resultados confusos.",
"welcome-page-overzealous-filter": "Nenhum resultado. Gostaria de <a href=\"{{URL}}\">redefinir o filtro</a> ?",
"powered-by-kiwix-html": "Desenvolvido por <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Pesquisar",
"book-filtering-all-categories": "Todas as categorias",
"book-filtering-all-languages": "Todos os idiomas",
"count-of-matching-books": "{{COUNT}} livro(s)",
"download": "Transferir",
"direct-download-link-text": "Direto",
"direct-download-alt-text": "Descarregar diretamente através de HTTP (S)",
"hash-download-link-text": "Verificação SHA-256",
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
"magnet-link-text": "Link magnético",
"magnet-alt-text": "Descarregar através do link Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Descarregar através de BitTorrent",
"library-opds-feed-all-entries": "Feed OPDS da biblioteca - Todas as entradas",
"filter-by-tag": "Filtrar por tag \"{{TAG}}\"",
"stop-filtering-by-tag": "Pare de filtrar pela tag \"{{TAG}}\"",
"library-opds-feed-parameterised": "Feed OPDS da biblioteca - entradas que correspondem a {{#LANG}}\nIdioma: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bem-vindo ao Kiwix Server",
"download-links-heading": "Links para download de <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Descarregar livros",
"preview-book": "Pré-visualização",
"unknown-error": "Erro desconhecido",
"book-category.wikibooks": "Wikilivros",
"book-category.wikinews": "Wikinotícias",
"book-category.wikipedia": "Wikipédia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversidade",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wikcionário",
"book-category.other": "Outro"
}

View File

@@ -24,9 +24,24 @@
"400-page-heading": "Heading of the 400 error page",
"404-page-title": "Title of the 404 error page",
"404-page-heading": "Heading of the 404 error page",
"new-404-page-title": "Title of the 404 error page",
"new-404-page-heading": "Heading of the 404 error page",
"404-img-text": "Fallback text for the image on the 404 error page",
"path-was-not-found": "Message telling that the URL path was not found (to be followed by the actual path)",
"404-advice.p1": "1st paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p2": "2nd paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p3": "3rd paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p4": "4th paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"404-advice.p5": "5th paragraph of the multiline advice on the 'Page not found' error page (see 404-advice.p1 through 404-advice.p5 for full text)",
"500-page-title": "Title of the 500 error page",
"500-page-heading": "Heading of the 500 error page",
"500-page-text": "Text of the 500 error page",
"external-link-detected": "Title & heading of the external link blocker page",
"caution-warning": "Warning of action that shouldn't be carried out carelessly",
"external-link-intro": "Message introducing the external link (to be followed by the actual link)",
"external-link-advice.p1": "1st paragraph of the multiline advice on the external link blocker page (see external-link-advice.p1 through external-link-advice.p3 for full text)",
"external-link-advice.p2": "2nd paragraph of the multiline advice on the external link blocker page (see external-link-advice.p1 through external-link-advice.p3 for full text)",
"external-link-advice.p3": "3rd paragraph of the multiline advice on the external link blocker page (see external-link-advice.p1 through external-link-advice.p3 for full text)",
"fulltext-search-unavailable": "Title of the error page returned when search is attempted in a book without fulltext search database",
"no-search-results": "Text of the error page returned when search is attempted in a book without fulltext search database",
"search-results-page-title": "Title of the search results page",
@@ -62,5 +77,24 @@
"download-links-title": "Title for no-js download page",
"preview-book": "Tooltip of book-tile leading to the book",
"non-translated-text": "{{ignored}}\nUsed to display text that is generated at runtime and cannot be translated. Nothing to translate about this one.",
"unknown-error": "Unknown error"
"unknown-error": "Unknown error",
"book-category.gutenberg": "Name for the category of books from the Gutenberg project",
"book-category.iFixit": "Name for the category of iFixit books",
"book-category.mooc": "Name for the category of MOOC books",
"book-category.phet": "Name for the category of Phet books",
"book-category.stack_exchange": "Name for the category of books from the Stack Exchange network books",
"book-category.ted": "Name for the category of Ted books",
"book-category.vikidia": "Name for the category of Vikidia books",
"book-category.wikibooks": "Name for the category of Wikibooks books books",
"book-category.wikihow": "Name for the category of wikiHow books",
"book-category.wikinews": "Name for the category of Wikinews books",
"book-category.wikipedia": "Name for the category of Wikipedia books",
"book-category.wikiquote": "Name for the category of Wikiquote books",
"book-category.wikisource": "Name for the category of Wikisource books",
"book-category.wikispecies": "Name for the category of Wikispecies books",
"book-category.wikiversity": "Name for the category of Wikiversity books",
"book-category.wikivoyage": "Name for the category of Wikivoyage books",
"book-category.wiktionary": "Name for the category of Wiktionary books",
"book-category.other": "Books not belonging to any special category are listed under this one",
"text-loading-content": "Text displayed while content is being loaded"
}

75
static/skin/i18n/ro.json Normal file
View File

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

View File

@@ -3,6 +3,7 @@
"authors": [
"Fenixs-ru",
"Kareyac",
"Lutece398",
"Okras",
"Pacha Tchernof",
"Razno0",
@@ -50,20 +51,30 @@
"count-of-matching-books": "{{COUNT}} книг(и)",
"download": "Скачать",
"direct-download-link-text": "Прямой",
"direct-download-alt-text": "прямая загрузка",
"hash-download-link-text": "Хэш Sha256",
"hash-download-alt-text": "скачать хэш",
"direct-download-alt-text": "Загрузка напрямую через HTTP(S)",
"hash-download-link-text": "Контрольная сумма SHA-256",
"hash-download-alt-text": "Показать контрольную сумму SHA-256 у файла",
"magnet-link-text": "Магнитная ссылка",
"magnet-alt-text": "скачать магнит",
"torrent-download-link-text": "Торрент-файл",
"torrent-download-alt-text": "скачать торрент",
"magnet-alt-text": "Скачать по Magnet-ссылке",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Скачать через BitTorrent",
"library-opds-feed-all-entries": "Канал библиотеки OPDS  все записи",
"filter-by-tag": "Фильтровать по тегу \"{{TAG}}\"",
"stop-filtering-by-tag": "Прекратить фильтрацию по тегу \"{{TAG}}\"",
"filter-by-tag": "Фильтровать по тегу \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Прекратить фильтрацию по тегу \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "Канал OPDS библиотеки – записи, соответствующие {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{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": "Предпросмотр",
"unknown-error": "Неизвестная ошибка"
"unknown-error": "Неизвестная ошибка",
"book-category.wikibooks": "Викиучебник",
"book-category.wikinews": "Викиновости",
"book-category.wikipedia": "Википедия",
"book-category.wikiquote": "Викицитатник",
"book-category.wikisource": "Викитека",
"book-category.wikispecies": "Викивиды",
"book-category.wikiversity": "Викиверситет",
"book-category.wikivoyage": "Викигид",
"book-category.wiktionary": "Викисловарь",
"book-category.other": "Другое"
}

View File

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

View File

@@ -9,6 +9,8 @@
"400-page-heading": "غلط ارداس",
"404-page-title": "مواد کائنی لبھیا",
"404-page-heading": "کائنی لبھا",
"new-404-page-title": "ورقہ کائنی لبھیا",
"404-img-text": "کائنی لبھا!",
"500-page-title": "اندرلا سرور نقص",
"500-page-heading": "اندرلا سرور نقص",
"search": "ڳولو",
@@ -22,5 +24,16 @@
"torrent-download-link-text": "ٹورنٹ فائل",
"torrent-download-alt-text": "ٹورںٹ ݙاؤن لوڈ کرو",
"download-links-title": "کتاب ڈاؤن لوڈ کرو",
"preview-book": "پیشگی ݙکھالا"
"preview-book": "پیشگی ݙکھالا",
"book-category.vikidia": "وکی ڈیا",
"book-category.wikibooks": "وکی کتاباں",
"book-category.wikinews": "وکی خبراں",
"book-category.wikipedia": "وکیپیڈیا",
"book-category.wikiquote": "وکی ٻول",
"book-category.wikisource": "وکی ماخذ",
"book-category.wikispecies": "وکی سپیشیز",
"book-category.wikiversity": "وکی ورسٹی",
"book-category.wikivoyage": "وکی سیرسپاٹا",
"book-category.wiktionary": "وکشنری",
"book-category.other": "ٻیا"
}

View File

@@ -10,7 +10,7 @@
"suggest-full-text-search": "vsebuje »{{{SEARCH_TERMS}}}« ...",
"no-such-book": "Ni take knjige: {{BOOK_NAME}}",
"too-many-books": "Preveč zahtevanih knjig ({{NB_BOOKS}}), omejitev je {{LIMIT}}",
"no-book-found": "Izbirnim merilom ne ustreza nobena knjiga",
"no-book-found": "Izbranim merilom ne ustreza nobena knjiga",
"url-not-found": "Zahtevanega URL-ja »{{url}}« v tem strežniku ni bilo mogoče najti.",
"suggest-search": "Preiščite celotno besedilo za <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Ni bilo mogoče izbrati naključnega članka :(",

View File

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

View File

@@ -2,6 +2,7 @@
"@metadata": {
"authors": [
"Jopparn",
"Larsa",
"Rofiatmustapha12",
"Sabelöga",
"WikiPhoenix"
@@ -47,20 +48,30 @@
"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",
"direct-download-alt-text": "Ladda ner direkt via HTTP(S)",
"hash-download-link-text": "SHA-256-kontrollsiffra",
"hash-download-alt-text": "Visa SHA-256-filens kontrollsiffra",
"magnet-link-text": "Magnetlänk",
"magnet-alt-text": "ladda ned magnet",
"torrent-download-link-text": "Torrent-fil",
"torrent-download-alt-text": "ladda ned torrent",
"magnet-alt-text": "Ladda ner via Magnet-länk",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Ladda ner via BitTorrent",
"library-opds-feed-all-entries": "Library OPDS Feed - Alla poster",
"filter-by-tag": "Filtrera efter taggen \"{{TAG}}\"",
"stop-filtering-by-tag": "Sluta filtrera efter taggen \"{{TAG}}\"",
"filter-by-tag": "Filtrera efter taggen \"{{{TAG}}}\"",
"stop-filtering-by-tag": "Sluta filtrera efter taggen \"{{{TAG}}}\"",
"library-opds-feed-parameterised": "Library OPDS Feed - poster som matchar {{#LANG}}\nSpråk: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTagg: {{TAG}} {{/TAG}}{{#Q}}\nFråga: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Välkommen till Kiwix Server",
"download-links-heading": "Nedladdningslänkar för <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Ladda ned bok",
"preview-book": "Förhandsgranska",
"unknown-error": "Okänt fel"
"unknown-error": "Okänt fel",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionary",
"book-category.other": "Övriga"
}

67
static/skin/i18n/sw.json Normal file
View File

@@ -0,0 +1,67 @@
{
"@metadata": {
"authors": [
"Joeamj",
"Muddyb",
"Peggy",
"Wangombe"
]
},
"name": "Kiswahili",
"suggest-full-text-search": "ina '{{{SEARCH_TERMS}}}}'...",
"no-such-book": "Hakuna kitabu kama hiki: {{BOOK_NAME}}",
"too-many-books": "Vitabu vingi mno vimeombwa ({{NB_BOOKS}}) ambapo kikomo ni {{LIMIT}}",
"no-book-found": "Hakuna kitabu kinacholingana na vigezo vya uteuzi",
"url-not-found": "URL iliyoombwa \"{{url}}\" haikupatikana kwenye seva hii.",
"suggest-search": "Tafuta maandishi kamili ya <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Lo! Imeshindwa kuchagua makala nasibu :(",
"invalid-raw-data-type": "{{DATATYPE}} si ombi halali la maudhui ghafi.",
"invalid-request": "URL iliyoombwa \"{{{url}}}\" si ombi halali.",
"no-value-for-arg": "Hakuna thamani iliyotolewa kwa hoja {{ARGUMENT}}",
"no-query": "Hakuna swali lililotolewa.",
"raw-entry-not-found": "Haiwezi kupata ingizo la {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Ombi batili",
"400-page-heading": "Ombi batili",
"404-page-title": "Maudhui hayajapatikana",
"404-page-heading": "Haijapatikana",
"500-page-title": "Hitilafu ya Ndani ya Seva",
"500-page-heading": "Hitilafu ya Ndani ya Seva",
"500-page-text": "Hitilafu ya ndani ya seva imetokea. Tunasikitika kwa hilo:/",
"fulltext-search-unavailable": "Utafutaji wa maandishi kamili haupatikani",
"no-search-results": "Injini ya utafutaji ya maandishi kamili haipatikani kwa maudhui haya.",
"search-results-page-title": "Tafuta: {{SEARCH_PATTERN}}",
"search-results-page-header": "Matokeo <b>{{START}}-{{END}}</b> ya <b>{{COUNT}}</b> ya <b>\"{{{SEARCH_PATTERN}}}}\"</b>",
"empty-search-results-page-header": "Hakuna matokeo yaliyopatikana ya <b>\"{{{SEARCH_PATTERN}}}}\"</b>",
"search-result-book-info": "kutoka kwa {{BOOK_TITLE}}",
"word-count": "Maneno {{COUNT}}",
"library-button-text": "Nenda katika wiki ya mwanzo",
"home-button-text": "Nenda kwenye ukurasa mkuu wa '{{BOOK_TITLE}}'",
"random-page-button-text": "Nenda kwa ukurasa uliochaguliwa kwa nasibu",
"searchbox-tooltip": "Tafuta '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Vitabu viwili au zaidi katika lugha tofauti vitashiriki katika utafutaji, jambo ambalo linaweza kusababisha matokeo ya kutatanisha.",
"welcome-page-overzealous-filter": "Hakuna matokeo. Je, ungependa <a href=\"{{URL}}\">kuweka upya kichujio</a> ?",
"powered-by-kiwix-html": "Inaendeshwa na <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Tafuta",
"book-filtering-all-categories": "Jamii Zote",
"book-filtering-all-languages": "Lugha zote",
"count-of-matching-books": "Vitabu {{COUNT}}",
"download": "Pakua",
"direct-download-link-text": "Moja kwa moja",
"direct-download-alt-text": "kupakua moja kwa moja",
"hash-download-link-text": "Sha256 heshi",
"hash-download-alt-text": "pakua heshi",
"magnet-link-text": "Kiungo cha sumaku",
"magnet-alt-text": "sumaku ya kupakua",
"torrent-download-link-text": "Faili ya Torrent",
"torrent-download-alt-text": "pakua torrent",
"library-opds-feed-all-entries": "Mlisho wa OPDS wa Maktaba - Maingizo yote",
"filter-by-tag": "Chuja kwa lebo \"{{TAG}}\"",
"stop-filtering-by-tag": "Acha kuchuja kwa lebo \"{{TAG}}\"",
"library-opds-feed-parameterised": "Mlisho wa OPDS wa Maktaba - maingizo yanayolingana {{#LANG}}\nLugha: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKitengo: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nSwali: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Karibu kwenye Seva ya Kiwix",
"download-links-heading": "Pakua viungo vya <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Pakua vitabu",
"preview-book": "Hakiki",
"unknown-error": "Hitilafu isiyojulikana",
"text-loading-content": "Inapakia Maudhui"
}

View File

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

View File

@@ -13,6 +13,21 @@
, "400-page-heading": "[I18N TESTING] -400 karma for an invalid request"
, "404-page-title": "[I18N TESTING] Not Found - Try Again"
, "404-page-heading": "[I18N TESTING] Content not found, but at least the server is alive"
, "new-404-page-title" : "Page [I18N] not [TESTING] found"
, "new-404-page-heading" : "[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave."
, "404-img-text": "[I18N] Not found! [TESTING]"
, "path-was-not-found": "[I18N TESTING] The requested path was not found (in fact, nothing was found instead, either):"
, "404-advice.p1": "Sh*t happens. [I18N TESTING] Take it easy!"
, "404-advice.p2": "[I18N TESTING] Try one of the following:"
, "404-advice.p3": "[I18N TESTING] Check the spelling of the URL path"
, "404-advice.p4": "[I18N TESTING] Press the dice button"
, "404-advice.p5": "Good luck! [I18N TESTING]"
, "external-link-detected" : "External [I18] Link [TESTING] Detected"
, "caution-warning" : "[I18N] C5n! [TESTING]"
, "external-link-intro" : "[I18N TESTING] The following link may lead you to a place from which you won't ever be able to return"
, "external-link-advice.p1": "[I18N TESTING] The link you're trying to access points past the end of the ZIM file."
, "external-link-advice.p2": "[I18N TESTING] If you are an optimist, you can attempt to open the link."
, "external-link-advice.p3": "[I18N TESTING] But we strongly recommend you to be reasonable and press your browser's back button."
, "library-button-text": "[I18N TESTING] Navigate to the welcome page"
, "home-button-text": "[I18N TESTING] Jump to the main page of '{{BOOK_TITLE}}'"
, "random-page-button-text": "[I18N TESTING] I am tired of determinism"
@@ -47,4 +62,23 @@
, "empty-search-results-page-header": "[I18N TESTING] No results were found for <b>\"{{{SEARCH_PATTERN}}}\"</b>"
, "search-result-book-info": "from [I18N TESTING] {{BOOK_TITLE}}"
, "word-count": "{{COUNT}} [I18N TESTING] words"
, "book-category.gutenberg": "[I18N] Gutenberg [TESTING]"
, "book-category.iFixit": "[I18N] iFixit [TESTING]"
, "book-category.mooc": "[I18N] MOOC [TESTING]"
, "book-category.phet": "[I18N] Phet [TESTING]"
, "book-category.stack_exchange": "[I18N] Stack Exchange [TESTING]"
, "book-category.ted": "[I18N] Ted [TESTING]"
, "book-category.vikidia": "[I18N] Vikidia [TESTING]"
, "book-category.wikibooks": "[I18N] Wikibooks [TESTING]"
, "book-category.wikihow": "[I18N] wikiHow [TESTING]"
, "book-category.wikinews": "[I18N] Wikinews [TESTING]"
, "book-category.wikipedia": "[I18N] Wikipedia [TESTING]"
, "book-category.wikiquote": "[I18N] Wikiquote [TESTING]"
, "book-category.wikisource": "[I18N] Wikisource [TESTING]"
, "book-category.wikispecies": "[I18N] Wikispecies [TESTING]"
, "book-category.wikiversity": "[I18N] Wikiversity [TESTING]"
, "book-category.wikivoyage": "[I18N] Wikivoyage [TESTING]"
, "book-category.wiktionary": "[I18N] Wiktionary [TESTING]"
, "book-category.other": "[I18N] Other [TESTING]"
, "text-loading-content": "[I18N TESTING] Loading content"
}

View File

@@ -3,19 +3,22 @@
"authors": [
"GuoPC",
"IceButBin",
"Kichin",
"Prmsh",
"StarrySky",
"Sunai",
"XtexChooser"
"XtexChooser",
"沈澄心"
]
},
"name": "英语",
"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": "抱歉!随机条目失败了 (⁠〒⁠﹏⁠〒⁠)",
"random-article-failure": "抱歉! 随机条目失败了 (⁠〒⁠﹏⁠〒⁠)",
"invalid-raw-data-type": "{{DATATYPE}} 对原请求无效。",
"invalid-request": "请求的URL无效{{{url}}}",
"no-value-for-arg": "参数{{ARGUMENT}}无值",
@@ -27,7 +30,7 @@
"404-page-heading": "未找到",
"500-page-title": "内部服务器错误",
"500-page-heading": "内部服务器错误",
"500-page-text": "内部服务器出现错误。真的十分抱歉 (;⁠ŏ⁠﹏⁠ŏ⁠)",
"500-page-text": "内部服务器出现错误。真的十分抱歉 (;⁠ŏ⁠﹏⁠ŏ~)",
"fulltext-search-unavailable": "全文搜索不可用",
"no-search-results": "全文搜索引擎不适用于该内容。",
"search-results-page-title": "搜索:{{SEARCH_PATTERN}}",
@@ -35,7 +38,7 @@
"empty-search-results-page-header": "未找到<b>“{{{SEARCH_PATTERN}}}”</b>的结果",
"search-result-book-info": "来自{{BOOK_TITLE}}",
"word-count": "{{COUNT}} 个字",
"library-button-text": "前往欢迎页面",
"library-button-text": "转到欢迎页面",
"home-button-text": "转到“{{BOOK_TITLE}}”的主页",
"random-page-button-text": "前往随机选择的页面",
"searchbox-tooltip": "搜索“{{BOOK_TITLE}}”",
@@ -48,17 +51,31 @@
"count-of-matching-books": "{{COUNT}} 本书",
"download": "下载",
"direct-download-link-text": "直接",
"direct-download-alt-text": "直接下載",
"hash-download-link-text": "Sha256 哈希值",
"hash-download-alt-text": "下载哈希值",
"direct-download-alt-text": "通过 HTTP(S) 直接下載",
"hash-download-link-text": "SHA-256 校验和",
"hash-download-alt-text": "显示 SHA-256 文件校验和",
"magnet-link-text": "磁力链接",
"magnet-alt-text": "下载磁力链接",
"torrent-download-link-text": "种子文件",
"torrent-download-alt-text": "下载种子文件",
"magnet-alt-text": "通过磁力链接下载",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "通过 BitTorrent 下载",
"library-opds-feed-all-entries": "图书馆 OPDS Feed - 所有条目",
"filter-by-tag": "按标签“{{TAG}}”过滤",
"stop-filtering-by-tag": "停止按标签“{{TAG}}”过滤",
"filter-by-tag": "按标签“{{{TAG}}}”过滤",
"stop-filtering-by-tag": "停止按标签“{{{TAG}}}”过滤",
"library-opds-feed-parameterised": "图书馆 OPDS Feed - 匹配的项目 {{#LANG}}\n语言{{LANG}} {{/LANG}}{{#CATEGORY}}\n分类{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n标签{{TAG}} {{/TAG}}{{#Q}}\n查询{{Q}} {{/Q}}",
"welcome-to-kiwix-server": "欢迎来到 Kiwix 服务器",
"preview-book": "预览"
"download-links-heading": "下载<b><i>{{BOOK_TITLE}}</i></b>的链接",
"download-links-title": "下载书籍",
"preview-book": "预览",
"unknown-error": "未知错误",
"book-category.wikibooks": "维基教科书",
"book-category.wikinews": "维基新闻",
"book-category.wikipedia": "维基百科",
"book-category.wikiquote": "维基语录",
"book-category.wikisource": "维基文库",
"book-category.wikispecies": "维基物种",
"book-category.wikiversity": "维基学院",
"book-category.wikivoyage": "维基导游",
"book-category.wiktionary": "维基词典",
"book-category.other": "其他",
"text-loading-content": "内容加载中"
}

View File

@@ -46,20 +46,39 @@
"count-of-matching-books": "{{COUNT}} 本書籍",
"download": "下載",
"direct-download-link-text": "直接",
"direct-download-alt-text": "直接下載",
"hash-download-link-text": "Sha256 雜湊",
"hash-download-alt-text": "下載雜湊",
"direct-download-alt-text": "直接透過 HTTPS下載",
"hash-download-link-text": "SHA-256 核對和",
"hash-download-alt-text": "顯示 SHA-256 檔案核對和",
"magnet-link-text": "Magnet 連結",
"magnet-alt-text": "下載 magnet",
"torrent-download-link-text": "Torrent 檔案",
"torrent-download-alt-text": "下載 torrent",
"magnet-alt-text": "透過磁力連結下載",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "透過 BitTorrent 下載",
"library-opds-feed-all-entries": "圖書館 OPDS 摘要 - 所有項目",
"filter-by-tag": "依標籤「{{TAG}}」篩選",
"stop-filtering-by-tag": "停止依標籤「{{TAG}}」篩選",
"filter-by-tag": "依標籤「{{{TAG}}}」篩選",
"stop-filtering-by-tag": "停止依標籤「{{{TAG}}}」篩選",
"library-opds-feed-parameterised": "圖書館 OPDS 摘要 - 項目符合 {{#LANG}}\n語言{{LANG}} {{/LANG}}{{#CATEGORY}}\n分類{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n標籤{{TAG}} {{/TAG}}{{#Q}}\n查詢{{Q}} {{/Q}}",
"welcome-to-kiwix-server": "歡迎來到 Kiwix 伺服器",
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
"download-links-title": "下載書籍",
"preview-book": "預覽",
"unknown-error": "不明錯誤"
"unknown-error": "不明錯誤",
"book-category.gutenberg": "古騰堡計劃",
"book-category.iFixit": "iFixit",
"book-category.mooc": "MOOC",
"book-category.phet": "PhET",
"book-category.stack_exchange": "Stack Exchange",
"book-category.ted": "Ted",
"book-category.vikidia": "Vikidia",
"book-category.wikibooks": "維基教科書",
"book-category.wikihow": "wikiHow",
"book-category.wikinews": "維基新聞",
"book-category.wikipedia": "維基百科",
"book-category.wikiquote": "維基語錄",
"book-category.wikisource": "維基文庫",
"book-category.wikispecies": "維基物種",
"book-category.wikiversity": "維基學院",
"book-category.wikivoyage": "維基導遊",
"book-category.wiktionary": "維基詞典",
"book-category.other": "其他",
"text-loading-content": "正在載入內容"
}

View File

@@ -121,7 +121,7 @@
.tagFilterLabel {
width: max-content;
padding: 10px;
padding: 7px;
font-family: roboto;
font-size: 12px;
margin: 0 0 0 17px;
@@ -152,23 +152,24 @@
.book__link {
text-decoration: none;
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.book__wrapper {
--tile-width: 250px;
--tile-border-width: 1px;
--tile-border-radius: 3px;
color: #444343;
height: 248px;
width: 250px;
height: 258px;
width: var(--tile-width);
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
display: grid;
grid-template-columns: 65px 1fr;
grid-template-rows: 70px 120px 1fr 1fr;
grid-gap: 5px;
border: var(--tile-border-width) solid #ececec;
border-radius: var(--tile-border-radius);
display: flex;
flex-direction: column;
justify-content: space-between;
transition: transform 0.25s;
gap: 4px;
}
.book__wrapper:hover {
@@ -177,8 +178,12 @@
.book__link__wrapper {
display: grid;
grid-template-areas:
"bookIcon bookHeader"
"bookDesc bookDesc"
;
grid-template-columns: 65px 1fr;
grid-template-rows: 70px 120px 1fr 1fr;
grid-template-rows: 70px 120px;
}
.book__icon {
@@ -189,7 +194,10 @@
align-items: center;
align-content: center;
justify-content: center;
width: 48px;
height: 48px;
margin: 10px 0 0 10px;
grid-area: bookIcon;
}
.book__header {
@@ -201,6 +209,7 @@
height: 100%;
align-items: center;
align-content: center;
grid-area: bookHeader;
}
.book__title {
@@ -211,28 +220,36 @@
}
.book__download {
font-size: 1.1rem;
margin: 3px 0;
cursor: pointer;
font-size: 1.2rem;
background: #00b4e4;
padding: 8px;
border-radius: 0 0 var(--tile-border-radius) var(--tile-border-radius);
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: var(--tile-width);
left: calc(0px - var(--tile-border-width));
text-decoration: none;
}
.book__download > img {
height: 16px;
width: 16px;
}
.book__download > span {
cursor: pointer;
text-decoration: none;
position: relative;
padding: 0 3px 1px;
font-family: roboto;
background: #00b4e4;
color: white;
box-shadow: 0 0 0 0;
border-radius: 2px;
}
.book__download > span:hover {
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1)
.book__download:hover {
background: royalblue;
}
.book__description {
grid-column: 1 / 3;
margin: 10px 10px 5px;
overflow: hidden;
display: -webkit-box;
@@ -243,6 +260,13 @@
font-size: 1.2rem;
color: #4d4d4d;
line-height: 1.25;
grid-area: bookDesc;
}
.book__meta {
display: flex;
justify-content: space-between;
padding: 0 10px 4px;
}
.book__languageTag {
@@ -255,7 +279,6 @@
color: black;
height: 25px;
width: 25px;
margin: 10px auto 0 10px;
border-radius: 5px;
font-size: 0.85rem;
}
@@ -266,8 +289,6 @@
font-size: 1.1rem;
justify-content: flex-end;
font-family: roboto;
margin-right: 10px;
margin-top: 10px;
overflow: hidden;
}

View File

@@ -78,15 +78,21 @@
return result;
}
function humanFriendlyNumStr(num, precision) {
const n = Math.abs(num).toFixed().length;
return num.toFixed(Math.max(0, precision - n));
}
const humanFriendlySize = (fileSize) => {
if (fileSize === 0) {
return '';
}
const units = ['bytes', 'kB', 'MB', 'GB', 'TB'];
let quotient = Math.floor(Math.log10(fileSize) / 3);
quotient = quotient < units.length ? quotient : units.length - 1;
fileSize /= (1000 ** quotient);
return `${+fileSize.toFixed(2)} ${units[quotient]}`;
const units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB'];
let quotient = Math.floor(Math.log2(fileSize) / 10);
quotient = Math.min(quotient, units.length - 1);
fileSize /= (1024 ** quotient);
const fileSizeStr = humanFriendlyNumStr(fileSize, 3);
return `${fileSizeStr} ${units[quotient]}`;
};
const humanFriendlyTitle = (title) => {
@@ -99,6 +105,14 @@
return '';
}
// Borrowed from https://stackoverflow.com/a/1912522
function htmlDecode(input){
var e = document.createElement('textarea');
e.innerHTML = input;
// handle case of empty input
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
function htmlEncode(str) {
return str.replace(/[\u00A0-\u9999<>\&]/gim, (i) => `&#${i.charCodeAt(0)};`);
}
@@ -115,9 +129,27 @@
function generateTagLink(tagValue) {
tagValue = tagValue.toLowerCase();
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
const tagMessage = $t("filter-by-tag", {TAG: humanFriendlyTagValue});
return `<span class='tag__link' aria-label='${tagMessage}' title='${tagMessage}' data-tag=${tagValue}>${humanFriendlyTagValue}</span>`
const tagMessage = $t("filter-by-tag", {TAG: tagValue});
const spanElement = document.createElement("span");
spanElement.className = 'tag__link';
spanElement.setAttribute('aria-label', tagMessage);
spanElement.setAttribute('title', tagMessage);
spanElement.setAttribute('data-tag', tagValue);
spanElement.innerHTML = htmlEncode(tagValue);
return spanElement.outerHTML;
}
function downloadButtonText(zimSize) {
return $t("download") + (zimSize ? ` - ${zimSize}`: '');
}
function downloadButtonHtml(url, zimSize) {
return url
? `<div class="book__download" data-link="${url}" data-size="${zimSize}">
<img src="${root}/skin/download-white.svg?KIWIXCACHEID">
<span ">${downloadButtonText(zimSize)}</span>
</div>`
: '';
}
function generateBookHtml(book, sort = false) {
@@ -138,7 +170,7 @@
const mulLangList = langCodesList.filter(x => languages.hasOwnProperty(x)).map(x => languages[x]);
language = mulLangList.join(', ');
}
const tags = getInnerHtml(book, 'tags');
const tags = htmlDecode(getInnerHtml(book, 'tags'));
const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))});
const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue));
const tagHtml = tagFilterLinks.join(' | ');
@@ -164,6 +196,7 @@
}
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
divTag.innerHTML = `
<div class="book__wrapper">
<a class="book__link" href="${viewerLink}" data-hover="Preview">
@@ -171,13 +204,15 @@
<div class="book__icon" ${faviconAttr}></div>
<div class="book__header">
<div id="book__title">${title}</div>
${downloadLink ? `<div class="book__download"><span data-link="${downloadLink}">${$t("download")} ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''}
</div>
<div class="book__description" title="${description}">${description}</div>
</div>
</a>
<div class="book__meta">
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
</div>
${downloadButtonHtml(downloadLink, humanFriendlyZimSize)}
</div></div>`;
return divTag;
}
@@ -203,7 +238,8 @@
let finalValue = (keysToURIEncode.indexOf(key) >= 0) ? encodeURIComponent(value) : value;
output += `&${key}=${finalValue}`;
}
return output;
// exclude first char so the first params are not prefixed with &
return output.substring(1);
}
/* hack for library.kiwix.org magnet links (created by MirrorBrain)
@@ -263,6 +299,7 @@
}
function insertModal(button) {
const downloadSize = button.getAttribute('data-size');
const downloadLink = button.getAttribute('data-link');
button.addEventListener('click', async (event) => {
event.preventDefault();
@@ -272,7 +309,7 @@
<div class="modal-heading">
<div class="modal-title">
<div>
Download
${downloadButtonText(downloadSize)}
</div>
</div>
<div onclick="closeModal()" class="modal-close-button">
@@ -432,7 +469,7 @@
booksToDelete.forEach(book => {iso.remove(book);});
books.forEach((book) => {
iso.insert(generateBookHtml(book, sort))
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download span`);
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download`);
if (downloadButton) {
insertModal(downloadButton);
}
@@ -486,9 +523,8 @@
function addTagElement(tagValue, resetFilter) {
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
tagElement.style.display = 'inline-block';
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
tagElement.innerHTML = `${humanFriendlyTagValue}`;
const tagMessage = $t("stop-filtering-by-tag", {TAG: humanFriendlyTagValue});
tagElement.innerHTML = htmlEncode(tagValue);
const tagMessage = $t("stop-filtering-by-tag", {TAG: tagValue});
tagElement.setAttribute('aria-label', tagMessage);
tagElement.setAttribute('title', tagMessage);
if (resetFilter)

View File

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

View File

@@ -2,12 +2,12 @@ const uiLanguages = [
{
"iso_code": "ar",
"self_name": "الإنجليزية",
"translation_count": 25
"translation_count": 43
},
{
"iso_code": "bn",
"self_name": "বাংলা",
"translation_count": 14
"translation_count": 24
},
{
"iso_code": "br",
@@ -22,32 +22,37 @@ const uiLanguages = [
{
"iso_code": "dag",
"self_name": "Silimiinsili",
"translation_count": 24
"translation_count": 48
},
{
"iso_code": "de",
"self_name": "Deutsch",
"translation_count": 57
"translation_count": 67
},
{
"iso_code": "el",
"self_name": "Αγγλικά",
"translation_count": 23
},
{
"iso_code": "en",
"self_name": "English",
"translation_count": 58
"translation_count": 77
},
{
"iso_code": "es",
"self_name": "español",
"translation_count": 48
"translation_count": 67
},
{
"iso_code": "fi",
"self_name": "suomi",
"translation_count": 22
"translation_count": 29
},
{
"iso_code": "fr",
"self_name": "Français",
"translation_count": 57
"translation_count": 68
},
{
"iso_code": "ha",
@@ -57,22 +62,27 @@ const uiLanguages = [
{
"iso_code": "he",
"self_name": "עברית",
"translation_count": 57
"translation_count": 69
},
{
"iso_code": "hi",
"self_name": "हिन्दी",
"translation_count": 49
"translation_count": 59
},
{
"iso_code": "hy",
"self_name": "Հայերեն",
"translation_count": 15
"translation_count": 25
},
{
"iso_code": "ia",
"self_name": "interlingua",
"translation_count": 49
"translation_count": 67
},
{
"iso_code": "id",
"self_name": "Bahasa Inggris",
"translation_count": 68
},
{
"iso_code": "ig",
@@ -82,7 +92,7 @@ const uiLanguages = [
{
"iso_code": "it",
"self_name": "italiano",
"translation_count": 34
"translation_count": 54
},
{
"iso_code": "ja",
@@ -92,7 +102,7 @@ const uiLanguages = [
{
"iso_code": "ko",
"self_name": "한국어",
"translation_count": 13
"translation_count": 64
},
{
"iso_code": "ku-latn",
@@ -102,22 +112,27 @@ const uiLanguages = [
{
"iso_code": "lb",
"self_name": "Lëtzebuergesch",
"translation_count": 22
"translation_count": 43
},
{
"iso_code": "mk",
"self_name": "македонски",
"translation_count": 57
"translation_count": 76
},
{
"iso_code": "ms",
"self_name": "Bahasa Melayu",
"translation_count": 14
},
{
"iso_code": "nb",
"self_name": "Engelsk",
"translation_count": 50
},
{
"iso_code": "nl",
"self_name": "Nederlands",
"translation_count": 49
"translation_count": 68
},
{
"iso_code": "nqo",
@@ -132,12 +147,27 @@ const uiLanguages = [
{
"iso_code": "pl",
"self_name": "Polski",
"translation_count": 31
"translation_count": 32
},
{
"iso_code": "pt-br",
"self_name": "Português",
"translation_count": 65
},
{
"iso_code": "pt",
"self_name": "português",
"translation_count": 67
},
{
"iso_code": "ro",
"self_name": "Engleză",
"translation_count": 67
},
{
"iso_code": "ru",
"self_name": "русский",
"translation_count": 57
"translation_count": 67
},
{
"iso_code": "sc",
@@ -152,7 +182,7 @@ const uiLanguages = [
{
"iso_code": "skr-arab",
"self_name": "سرائیکی",
"translation_count": 20
"translation_count": 31
},
{
"iso_code": "sl",
@@ -162,12 +192,17 @@ const uiLanguages = [
{
"iso_code": "sq",
"self_name": "Shqip",
"translation_count": 49
"translation_count": 67
},
{
"iso_code": "sv",
"self_name": "Svenska",
"translation_count": 57
"translation_count": 67
},
{
"iso_code": "sw",
"self_name": "Kiswahili",
"translation_count": 58
},
{
"iso_code": "te",
@@ -181,12 +216,12 @@ const uiLanguages = [
},
{
"iso_code": "zh-hans",
"self_name": "英语",
"translation_count": 54
"self_name": "简体中文",
"translation_count": 68
},
{
"iso_code": "zh-hant",
"self_name": "繁體中文",
"translation_count": 57
"translation_count": 76
}
]

View File

@@ -38,7 +38,7 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 160px;
max-width: 30ch;
}
.kiwix .kiwix_centered {

View File

@@ -92,14 +92,17 @@ function makeJSLink(jsCodeString, linkText, linkAttr="") {
// in the JS code we have to URI-encode a second time.
// (see https://stackoverflow.com/questions/33721510)
const uriEncodedJSCode = encodeURIComponent(jsCodeString);
return `<a ${linkAttr} href="javascript:${uriEncodedJSCode}">${linkText}</a>`;
const linkPlainText = htmlDecode(linkText, "text/html");
linkAttr += ` href="javascript:${uriEncodedJSCode}"`;
linkAttr += ` title="${linkPlainText}"`;
return `<a ${linkAttr}>${linkText}</a>`;
}
function suggestionsApiURL()
{
const uriEncodedBookName = encodeURIComponent(currentBook);
const userLang = viewerState.uiLanguage;
return `${root}/suggest?userlang=${userLang}&content=${uriEncodedBookName}`;
return `${root}/suggest?userlang=${userLang}&mode=smart&content=${uriEncodedBookName}`;
}
function setTitle(element, text) {
@@ -183,9 +186,13 @@ function closeSuggestions() {
}
}
function setSearchQuery(text) {
document.getElementById("kiwixsearchbox").value = text;
}
function updateSearchBoxForLocationChange() {
closeSuggestions();
document.getElementById("kiwixsearchbox").value = getSearchPattern();
setSearchQuery(getSearchPattern());
}
function updateSearchBoxForBookChange() {
@@ -259,22 +266,7 @@ function handle_location_hash_change() {
}
function translateErrorPageIfNeeded() {
const cw = contentIframe.contentWindow;
if ( cw.KIWIX_RESPONSE_TEMPLATE && cw.KIWIX_RESPONSE_DATA ) {
const template = htmlDecode(cw.KIWIX_RESPONSE_TEMPLATE);
// cw.KIWIX_RESPONSE_DATA belongs to the iframe context and running
// I18n.render() on it directly in the top context doesn't work correctly
// because the type checks (obj.__proto__ == ???.prototype) in
// I18n.instantiateParameterizedMessages() always fail (String.prototype
// refers to different objects in different contexts).
// Work arround that issue by copying the object into our context.
const params = JSON.parse(JSON.stringify(cw.KIWIX_RESPONSE_DATA));
const html = I18n.render(template, params);
const htmlDoc = new DOMParser().parseFromString(html, "text/html");
cw.document.documentElement.innerHTML = htmlDoc.documentElement.innerHTML;
}
translatePageInWindow(contentIframe.contentWindow);
}
function handle_content_url_change() {
@@ -310,6 +302,12 @@ function blockLink(url) {
: url;
}
function urlMustBeHandledByAnExternalApp(url) {
const WHITELISTED_URL_SCHEMATA = ['http:', 'https:', 'about:', 'javascript:'];
return WHITELISTED_URL_SCHEMATA.indexOf(url.protocol) == -1;
}
function isExternalUrl(url) {
if ( url.startsWith(window.location.origin) )
return false;
@@ -329,20 +327,46 @@ function getRealHref(target) {
return target_href;
}
function setHrefAvoidingWombatRewriting(target, url) {
const old_no_rewrite = target._no_rewrite;
target._no_rewrite = true;
target.setAttribute("href", url);
target._no_rewrite = old_no_rewrite;
}
function linkShouldBeOpenedInANewWindow(linkElement, mouseEvent) {
return linkElement.getAttribute("target") == "_blank"
|| mouseEvent.ctrlKey
|| mouseEvent.shiftKey
|| mouseEvent.metaKey /* on Macs */;
}
function goingToOpenALinkToAnUndisplayableResource(url) {
return !navigator.pdfViewerEnabled && url.pathname.endsWith('.pdf');
}
function onClickEvent(e) {
const iframeDocument = contentIframe.contentDocument;
const target = matchingAncestorElement(e.target, iframeDocument, "a");
if (target !== null && "href" in target) {
const target_href = getRealHref(target);
if (isExternalUrl(target_href)) {
const target_url = new URL(target_href, iframeDocument.location);
const isExternalAppUrl = urlMustBeHandledByAnExternalApp(target_url);
if ( (isExternalAppUrl && !viewerSettings.linkBlockingEnabled)
|| goingToOpenALinkToAnUndisplayableResource(target_url) ) {
target.setAttribute("target", "_blank");
}
if (isExternalAppUrl || isExternalUrl(target_href)) {
const possiblyBlockedLink = blockLink(target_href);
if ( e.ctrlKey || e.shiftKey ) {
if ( linkShouldBeOpenedInANewWindow(target, e) ) {
// The link will be loaded in a new tab/window - update the link
// and let the browser handle the rest.
target.setAttribute("href", possiblyBlockedLink);
setHrefAvoidingWombatRewriting(target, possiblyBlockedLink);
} else {
// Load the external URL in the viewer window (rather than iframe)
contentIframe.contentWindow.parent.location = possiblyBlockedLink;
e.preventDefault();
}
}
}
@@ -389,6 +413,10 @@ function setup_external_link_blocker() {
let viewerSetupComplete = false;
function on_content_load() {
const loader = document.getElementById("kiwix__loader");
contentIframe.classList.remove("hidden");
loader.style.display = "none";
if ( viewerSetupComplete ) {
handle_content_url_change();
}
@@ -440,7 +468,8 @@ function setupSuggestions() {
resultItem: {
element: (item, data) => {
const uriEncodedBookName = encodeURIComponent(currentBook);
let url;
const linkText = htmlDecode(data.value.label);
let url, modifiedQuery;
if (data.value.kind == "path") {
// The double quote and backslash symbols are included in the list
// of special symbols to URI-encode so that the resulting URL can
@@ -449,15 +478,18 @@ function setupSuggestions() {
const path = htmlDecode(data.value.path);
const quasiUriEncodedPath = quasiUriEncode(path, '#?"\\');
url = `/content/${uriEncodedBookName}/${quasiUriEncodedPath}`;
} else {
} else if (data.value.kind == "pattern") {
const pattern = encodeURIComponent(htmlDecode(data.value.value));
url = `/search?content=${uriEncodedBookName}&pattern=${pattern}`;
} else { // data.value.kind == "modifiedquery"
modifiedQuery = htmlDecode(linkText);
}
// url can't contain any double quote and/or backslash symbols
// since they should have been URI-encoded. Therefore putting it
// inside double quotes should result in valid javascript.
const jsAction = `gotoUrl("${url}")`;
const linkText = htmlDecode(data.value.label);
const jsAction = url
? `gotoUrl("${url}")`
: `setSearchQuery("${modifiedQuery}")`;
item.innerHTML = makeJSLink(jsAction, linkText, 'class="suggest"');
},
highlight: "autoComplete_highlight",
@@ -577,6 +609,12 @@ function updateUIText() {
setTitle(document.getElementById("kiwix_serve_taskbar_random_button"),
$t("random-page-button-text"));
// Add translation for loading text as soon as translations are available
const loadingTextElement = document.getElementById("kiwix__loading_text");
if (loadingTextElement) {
loadingTextElement.textContent = $t("text-loading-content");
}
}
function finishViewerSetupOnceTranslationsAreLoaded()

View File

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

View File

@@ -22,6 +22,7 @@
id="headFeedLink"
href="{{root}}/catalog/v2/entries"
/>
<link rel="search" type="application/opensearchdescription+xml" href="{{root}}/catalog/searchdescription.xml" />
<link rel="apple-touch-icon" sizes="180x180" href="{{root}}/skin/favicon/apple-touch-icon.png?KIWIXCACHEID">
<link rel="icon" type="image/png" sizes="32x32" href="{{root}}/skin/favicon/favicon-32x32.png?KIWIXCACHEID">
<link rel="icon" type="image/png" sizes="16x16" href="{{root}}/skin/favicon/favicon-16x16.png?KIWIXCACHEID">
@@ -92,7 +93,7 @@
</div>
</div>
<form id='kiwixSearchForm' class='kiwixNav__SearchForm'>
<input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
<input type="text" name="q" accesskey="s" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
<span class="kiwixButton tagFilterLabel"></span>
<input type="submit" class="kiwixButton kiwixButtonHover" id="searchButton" value="Search"/>
</form>

View File

@@ -50,20 +50,11 @@
pointer-events: none;
}
.book__link__wrapper {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.book__link {
grid-row: 2 / 3;
}
.kiwixHomeBody__results {
flex-basis: 100%;
}
#book__title>a, .book__download a {
#book__title>a {
text-decoration: none;
all: unset;
}
@@ -90,7 +81,7 @@
</div>
</div>
<form id='kiwixSearchForm' class='kiwixNav__SearchForm' action="{{root}}/nojs">
<input type="text" name="q" placeholder="{{translations.search}}" id="searchFilter" class='kiwixSearch filter' value="{{searchQuery}}">
<input type="text" name="q" accesskey="s" placeholder="{{translations.search}}" id="searchFilter" class='kiwixSearch filter' value="{{searchQuery}}">
<input type="submit" class="kiwixButton kiwixButtonHover" value="{{translations.search}}"/>
</form>
</div>
@@ -117,25 +108,32 @@
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
{{#books}}
<div class="book__wrapper">
<div class="book__link__wrapper">
<div class="book__icon" {{faviconAttr}}></div>
<div class="book__header">
<div id="book__title"><a href="{{root}}/content/{{id}}">{{title}}</a></div>
{{#downloadAvailable}}
<div class="book__download"><span><a href="{{root}}/nojs/download/{{id}}">{{translations.download}}</a></span></div>
{{/downloadAvailable}}
</div>
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
<div class="book__description" title="{{description}}">{{description}}</div>
</a>
</div>
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
<div class="book__tags"><div class="book__tags--wrapper">
{{#tagList}}
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
{{/tagList}}
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
<div class="book__link__wrapper">
<div class="book__icon" {{faviconAttr}}></div>
<div class="book__header">
<div id="book__title">{{title}}</div>
</div>
</div>
<div class="book__description" title="{{description}}">{{description}}</div>
</div>
</a>
<div class="book__meta">
<div class="book__languageTag" title="{{langTag.langFullString}}" aria-label="{{langTag.langFullString}}">{{langTag.langShortString}}</div>
<div class="book__tags"><div class="book__tags--wrapper">
{{#tagList}}
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
{{/tagList}}
</div>
</div>
</div>
{{#downloadAvailable}}
<div>
<a class="book__download" href="{{root}}/nojs/download/{{id}}">
<img src="{{root}}/skin/download-white.svg?KIWIXCACHEID">
<span>{{translations.download}}</span>
</a>
</div>
{{/downloadAvailable}}
</div>
{{/books}}
</div>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>{{PAGE_TITLE}}</title>
<link type="text/css" href="{{root}}/skin/error.css?KIWIXCACHEID" rel="Stylesheet" />
{{#KIWIX_RESPONSE_DATA}} <script>
window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";
window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};
</script>{{/KIWIX_RESPONSE_DATA}}
</head>
<body>
<header>
<img src="{{root}}/skin/404.svg?KIWIXCACHEID"
alt="{{404_img_text}}"
aria-label="{{404_img_text}}"
title="{{404_img_text}}">
</header>
<section class="intro">
<h1>{{PAGE_HEADING}}</h1>
<p>{{path_was_not_found_msg}}</p>
<p><code>{{url_path}}</code></p>
</section>
<section class="advice">
<p>{{advice.p1}}</p>
<p class="list-intro">{{advice.p2}}</p>
<ul>
<li>{{advice.p3}}</li>
<li>{{advice.p4}}</li>
</ul>
<p>{{advice.p5}}</p>
</section>
</body>
</html>

View File

@@ -33,6 +33,10 @@
</head>
<body style="margin:0" onload="setupViewer()">
<div class="loader" id="kiwix__loader">
<div class="spinner"></div>
<span id="kiwix__loading_text">Loading content...</span>
</div>
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
<div id="kiwixtoolbar" class="ui-widget-header">
<div class="kiwix_centered">
@@ -45,14 +49,22 @@
<input type="checkbox" id="kiwix_button_show_toggle">
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?KIWIXCACHEID" alt=""></label>
<div class="kiwix_button_cont">
<a id="kiwix_serve_taskbar_library_button" title="Go to welcome page" aria-label="Go to welcome page" href="./"><button>&#x1f3e0;</button></a>
<a id="kiwix_serve_taskbar_library_button"
title="Go to welcome page"
accesskey="w"
aria-label="Go to welcome page"
href="./">
<button>&#x1f3e0;</button>
</a>
<span id="kiwix_serve_taskbar_book_ui_group">
<a id="kiwix_serve_taskbar_home_button"
title="Go to the main page of the current book"
accesskey="h"
aria-label="Go to the main page of the current book"
onclick="gotoMainPageOfCurrentBook()"></a>
<a id="kiwix_serve_taskbar_random_button"
title="Go to a randomly selected page"
accesskey="r"
aria-label="Go to a randomly selected page"
onclick="gotoRandomPage()">
<button>&#x1F3B2;</button>
@@ -70,7 +82,7 @@
</div>
</div>
<iframe id="content_iframe"
<iframe id="content_iframe" class="hidden"
referrerpolicy="no-referrer"
onload="on_content_load()"
src="./skin/blank.html?KIWIXCACHEID" title="ZIM content" width="100%"

View File

@@ -15,11 +15,19 @@ struct XMLDoc : pugi::xml_document
} // unnamed namespace
#if _WIN32
# define DATA_ABS_PATH "C:\\data\\zim"
# define ZARA_ABS_PATH "C:\\data\\zim\\zara.zim"
#else
# define DATA_ABS_PATH "/data/zim"
# define ZARA_ABS_PATH "/data/zim/zara.zim"
#endif
TEST(BookTest, updateFromXMLTest)
{
const XMLDoc xml(R"(
<book id="zara"
path="./zara.zim"
path="zara.zim"
url="https://who.org/zara.zim"
title="Catch an infection in 24 hours"
description="Complete guide to contagious diseases"
@@ -40,9 +48,9 @@ TEST(BookTest, updateFromXMLTest)
)");
kiwix::Book book;
book.updateFromXml(xml.child("book"), "/data/zim");
book.updateFromXml(xml.child("book"), DATA_ABS_PATH);
EXPECT_EQ(book.getPath(), "/data/zim/zara.zim");
EXPECT_EQ(book.getPath(), ZARA_ABS_PATH);
EXPECT_EQ(book.getUrl(), "https://who.org/zara.zim");
EXPECT_EQ(book.getTitle(), "Catch an infection in 24 hours");
EXPECT_EQ(book.getDescription(), "Complete guide to contagious diseases");

View File

@@ -1,4 +1,4 @@
<library version="20110515">
<book id="5dc0b3af-5df2-0925-f0ca-d2bf75e78af6" path="example.zim" title="Wikibooks" description="testZim" language="eng" creator="test" publisher="test" tags="_ftindex:yes;_ftindex:yes;_pictures:yes;_videos:yes;_details:yes" date="2021-04-17" mediaCount="22" size="253" />
<book id="5dc0b3af-5df2-0925-f0ca-d2bf75e78af6" path="example.zim" title="Wikibooks" description="testZim" language="eng" creator="test" publisher="test" name="bookname_of_example_zim" tags="_ftindex:yes;_ftindex:yes;_pictures:yes;_videos:yes;_details:yes" date="2021-04-17" mediaCount="22" size="253" />
<book id="6f1d19d0-633f-087b-fb55-7ac324ff9baf" path="zimfile.zim" title="Ray Charles" description="Wikipedia articles about Ray Charles" language="eng" creator="Wikipedia" publisher="Kiwix" name="wikipedia_en_ray_charles" flavour="_mini" tags="wikipedia;_category:wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes" date="2020-03-31" articleCount="129" mediaCount="45" size="555" />
</library>

Some files were not shown because too many files have changed in this diff Show More