mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-01-02 03:18:04 -05:00
Compare commits
210 Commits
13.1.0
...
smart_sugg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8fc60ed2f | ||
|
|
222e4396c7 | ||
|
|
4c480952d1 | ||
|
|
79479788f9 | ||
|
|
3cd1f7854a | ||
|
|
58a211d01d | ||
|
|
07fc40da5a | ||
|
|
d961447e1e | ||
|
|
c9ebeb7b96 | ||
|
|
2d73ed31a9 | ||
|
|
6a0349e575 | ||
|
|
6d80edc04a | ||
|
|
b3a33747f0 | ||
|
|
f47490e1bc | ||
|
|
1ce909ae68 | ||
|
|
5eb31d7286 | ||
|
|
107421cdab | ||
|
|
bd474b9720 | ||
|
|
e66ba1a532 | ||
|
|
c1e58331d7 | ||
|
|
d776077c5f | ||
|
|
b8e997f805 | ||
|
|
b7421d7dae | ||
|
|
a0c99f879b | ||
|
|
c7e86c9dbb | ||
|
|
ad58a501b0 | ||
|
|
a55e8565d1 | ||
|
|
610b8cbb2a | ||
|
|
e087f1c82f | ||
|
|
e5d3e6ff07 | ||
|
|
d0aeac64d0 | ||
|
|
5da88c0ad7 | ||
|
|
c7d3a38a3e | ||
|
|
6b74395455 | ||
|
|
2eea6136d6 | ||
|
|
64eb0d10d6 | ||
|
|
664944f16c | ||
|
|
d34a0c5bf0 | ||
|
|
bb65d77229 | ||
|
|
98849831da | ||
|
|
2e3eae5615 | ||
|
|
93ace5cf45 | ||
|
|
cb777ed836 | ||
|
|
27e7840cce | ||
|
|
99c28b72b5 | ||
|
|
81b579cdcb | ||
|
|
f693f700bc | ||
|
|
50f04d7060 | ||
|
|
8fdaa5f4db | ||
|
|
297627fbc7 | ||
|
|
a3708c68ce | ||
|
|
a809c671fd | ||
|
|
31477bc99b | ||
|
|
6c37e2827e | ||
|
|
9138f91c31 | ||
|
|
9bd568fe0e | ||
|
|
eca7cf86e6 | ||
|
|
77f4fd7447 | ||
|
|
84ebee899c | ||
|
|
9eed5da3be | ||
|
|
20abebd623 | ||
|
|
58a1af85b9 | ||
|
|
585f55d885 | ||
|
|
8b00c2eb22 | ||
|
|
c8bddd6cf4 | ||
|
|
5d1b6274a8 | ||
|
|
0de9bd0a99 | ||
|
|
b62274efdd | ||
|
|
4a1498d8df | ||
|
|
f6df2342cf | ||
|
|
8bbda99cab | ||
|
|
95529d2c0a | ||
|
|
b80699916d | ||
|
|
534916929d | ||
|
|
02ab2ce5a5 | ||
|
|
8930095c52 | ||
|
|
bef3ec7694 | ||
|
|
9057686a25 | ||
|
|
723dd977fe | ||
|
|
0b87d4fe04 | ||
|
|
ea31e2f42f | ||
|
|
01bda6b2c0 | ||
|
|
de64a5a724 | ||
|
|
90dd1cb3f0 | ||
|
|
0b14fda94d | ||
|
|
fe965faf1b | ||
|
|
6ad1776242 | ||
|
|
cbfd3ec7c4 | ||
|
|
f6765137e7 | ||
|
|
c24e04c8da | ||
|
|
327fec1877 | ||
|
|
c8524b95bc | ||
|
|
0ac3130b0d | ||
|
|
425ae1efae | ||
|
|
920d603a89 | ||
|
|
f5c91cc272 | ||
|
|
3cdc036858 | ||
|
|
29bfaa5c5b | ||
|
|
bec80e8091 | ||
|
|
2da9801bac | ||
|
|
16ebc6611b | ||
|
|
d5a44b913e | ||
|
|
a63a162c58 | ||
|
|
c29cd8cf3b | ||
|
|
04bf1be9d6 | ||
|
|
59054aa5ad | ||
|
|
1b8dde0115 | ||
|
|
9d0f6a3170 | ||
|
|
c16ed0aa4c | ||
|
|
3d95b386c6 | ||
|
|
a3f5a654f2 | ||
|
|
801b1df304 | ||
|
|
2b8a071c6f | ||
|
|
00fae37f2d | ||
|
|
846404e959 | ||
|
|
fbcd160efd | ||
|
|
196185dd73 | ||
|
|
affb996769 | ||
|
|
418abbcefa | ||
|
|
00867a13f6 | ||
|
|
e096c7e2fd | ||
|
|
69341eab47 | ||
|
|
082727ebb6 | ||
|
|
75a4f8b806 | ||
|
|
2eaa1c4649 | ||
|
|
199a10d093 | ||
|
|
4812fb18f6 | ||
|
|
940818d801 | ||
|
|
5182a66b19 | ||
|
|
b688aa294a | ||
|
|
27ad77c566 | ||
|
|
7677f76854 | ||
|
|
513a8d1383 | ||
|
|
be464a5986 | ||
|
|
c2042c3be8 | ||
|
|
8d480c8b6d | ||
|
|
82ff88f5d8 | ||
|
|
2535f210b3 | ||
|
|
cb0a2c234a | ||
|
|
5a73a75798 | ||
|
|
0ea756c42a | ||
|
|
7108dfa9c2 | ||
|
|
9fd8e81de2 | ||
|
|
566b40a2f8 | ||
|
|
ff6d8a4b30 | ||
|
|
f456ce3e93 | ||
|
|
ece40966f1 | ||
|
|
65a777d4ed | ||
|
|
42295c9010 | ||
|
|
e8afcbe6ae | ||
|
|
c46cd403ae | ||
|
|
af96b19bd1 | ||
|
|
8a00e9383d | ||
|
|
964131ce47 | ||
|
|
97832c8436 | ||
|
|
beab8d7041 | ||
|
|
75bddbf725 | ||
|
|
5927550a36 | ||
|
|
135c6f875d | ||
|
|
83101679a0 | ||
|
|
ae4b652fb2 | ||
|
|
01b94418eb | ||
|
|
a1ce3d10b1 | ||
|
|
b7eadf95bf | ||
|
|
a6cf161341 | ||
|
|
618a718645 | ||
|
|
c2cc4c39f1 | ||
|
|
8477e04ffa | ||
|
|
5345d43017 | ||
|
|
843adb3397 | ||
|
|
4fe4a88574 | ||
|
|
6ee09114eb | ||
|
|
7366938785 | ||
|
|
84405b1318 | ||
|
|
a0c4118fd3 | ||
|
|
72147aec5b | ||
|
|
016072292c | ||
|
|
2964cc5e92 | ||
|
|
8d766335b4 | ||
|
|
5450bcd3c2 | ||
|
|
a0b66eae0c | ||
|
|
22c75245a5 | ||
|
|
f40c3426a5 | ||
|
|
8e6569362c | ||
|
|
eb328ed73d | ||
|
|
21e3c5c19f | ||
|
|
f0927fec49 | ||
|
|
66693cd73e | ||
|
|
be8a60c330 | ||
|
|
8009edd349 | ||
|
|
3733e506c1 | ||
|
|
9fe81e9bce | ||
|
|
4ab6215046 | ||
|
|
ff88430227 | ||
|
|
922c138809 | ||
|
|
fa9ebf55fc | ||
|
|
bc9b5a0354 | ||
|
|
719e947ddf | ||
|
|
e3fffd9b23 | ||
|
|
6ef4f6396e | ||
|
|
d8b4c1584c | ||
|
|
1fc006f639 | ||
|
|
a8368b3a0d | ||
|
|
068555de38 | ||
|
|
0168764f4c | ||
|
|
181893d31a | ||
|
|
5b9daf0d9d | ||
|
|
4e64d26ede | ||
|
|
5e669cd65c | ||
|
|
2749564424 |
156
.github/workflows/ci.yml
vendored
156
.github/workflows/ci.yml
vendored
@@ -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 }}
|
||||
|
||||
58
.github/workflows/package.yml
vendored
58
.github/workflows/package.yml
vendored
@@ -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:
|
||||
|
||||
28
ChangeLog
28
ChangeLog
@@ -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
17
debian/control
vendored
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -10,7 +10,8 @@ headers = [
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h',
|
||||
'tools.h',
|
||||
'version.h'
|
||||
'version.h',
|
||||
'i18n.h'
|
||||
]
|
||||
|
||||
install_headers(headers, subdir:'kiwix')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
10
kiwix.pc.in
10
kiwix.pc.in
@@ -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@
|
||||
84
meson.build
84
meson.build
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include "server/i18n.h"
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
83
src/server/i18n_utils.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <cctype>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "i18n.h"
|
||||
#include "i18n_utils.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
|
||||
@@ -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}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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ɩyɛ"},
|
||||
{"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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, "_");
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ std::string ucAll(const std::string& word);
|
||||
std::string lcAll(const std::string& word);
|
||||
std::string ucFirst(const std::string& word);
|
||||
std::string lcFirst(const std::string& word);
|
||||
|
||||
/* This function is broken, related Github issue
|
||||
* https://github.com/kiwix/libkiwix/issues/1188 */
|
||||
std::string toTitle(const std::string& word);
|
||||
|
||||
std::string normalize(const std::string& word);
|
||||
|
||||
@@ -15,6 +15,7 @@ skin/i18n/he.json
|
||||
skin/i18n/hi.json
|
||||
skin/i18n/hy.json
|
||||
skin/i18n/ia.json
|
||||
skin/i18n/id.json
|
||||
skin/i18n/ig.json
|
||||
skin/i18n/it.json
|
||||
skin/i18n/ja.json
|
||||
@@ -23,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
|
||||
|
||||
@@ -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
1
static/skin/404.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.4 KiB |
@@ -23,6 +23,9 @@
|
||||
|
||||
.autoComplete_wrapper > ul {
|
||||
position: absolute;
|
||||
min-width: 100%;
|
||||
width: fit-content;
|
||||
max-width: 600px;
|
||||
max-height: 226px;
|
||||
overflow-y: scroll;
|
||||
top: 100%;
|
||||
@@ -60,6 +63,11 @@
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li > a {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.autoComplete_wrapper > ul > li::selection {
|
||||
color: rgba(#ffffff, 0);
|
||||
background-color: rgba(#ffffff, 0);
|
||||
|
||||
1
static/skin/blocklink.svg
Normal file
1
static/skin/blocklink.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1742.79 1984.21" xmlns="http://www.w3.org/2000/svg"><ellipse cx="667.97" cy="1872.93" fill="#c7c8ca" rx="649.71" ry="111.28"/><path d="m933.76 1775.81c0-29.5-23.92-53.42-53.42-53.42h-163.28c-24.37 0-45.97-15.91-52.98-39.26l-105.96-347.65 18.1-9.63c251.03-105.38 519.58 82.75 706.54-97.93l1.17-1.17c23.21-21.02 46.27-42.47 114.72.29 73.56 46.12 236 166.53 309.71 372.32 0 0 44.66-15.91 32.25-70.49-12.41-54.73-102.89-212.36-287.96-352.18-33.86-25.69-64.36-47-89.76-64.07 36.93-158.21-155.14-349.11-342.69-259.21-134.57-126.54-284.6-178.2-422.67-176.16-365.6 5.11-647.58 386.04-344.3 749.45l.58.58c4.67 5.55 9.49 11.09 14.3 16.64 19.7 23.64 32.69 44.51 43.93 81.29l90.34 296.57h-31.96c-29.48 0-53.42 23.94-53.42 53.42h121.43l204.04 96.47c12.55-26.71 1.17-58.53-25.4-71.08l-53.56-25.25h153.83c0-29.48-23.94-53.42-53.42-53.42h-139.38c-24.37 0-45.97-15.91-52.98-39.26l-71.66-235.42c-17.81-61.88 23.21-102.31 61.74-107.13 35.17-4.38 52.83 18.39 69.47 73.27l63.63 208.85h-31.96c-29.48 0-53.42 23.94-53.42 53.42h121.43l204.04 96.47c12.55-26.71 1.17-58.53-25.4-71.08l-53.56-25.25h177.88z"/><path d="m545.68 2.53h52.19v1441.85h-52.19z" fill="#f89a16" transform="matrix(.9855856 -.16917749 .16917749 .9855856 -114.16 107.17)"/><path d="m581.21 624.21-55.8-17.21-93.34-544.54 53.51 3.6z" fill="#da7c2b"/><path d="m981.62 279.44c-30.85 1.91-83.72 3.58-83.72 3.58l79.01-31.06-43.25-251.96-933.66 160.24 56.63 330 68.11 13.66s-38.47 19-60.89 28.37l22.52 131.23 933.66-160.24z" fill="#f89a16"/><circle cx="1144.16" cy="1031.93" fill="#fff" r="82.26" transform="matrix(.70710678 -.70710678 .70710678 .70710678 -394.57 1111.29)"/><circle cx="1124.15" cy="1004.52" r="52.63" transform="matrix(.41786707 -.90850818 .90850818 .41786707 -258.21 1606.05)"/><g fill="#fff"><path d="m387.43 559.95-69.59-57.58 107.75-360.2 93.69-15.45 226.75 308.17-45.64 75.04-312.97 50.02zm-11.05-75.33 25.78 21.33 266.91-42.65 15.63-25.7-187.97-255.46-31.41 5.18-88.94 297.31z"/><path d="m526.16 300.48 5.44 89.61-20.72 3.32-28.24-85.96-9.08-47.73 43.52-6.98 9.08 47.73zm18.46 103.04 7.02 36.88-41.43 6.64-7.02-36.88z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
6
static/skin/download-white.svg
Normal file
6
static/skin/download-white.svg
Normal 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
159
static/skin/error.css
Normal file
@@ -0,0 +1,159 @@
|
||||
@font-face {
|
||||
font-family:"DM Sans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src : url('../skin/fonts/DMSans-Regular.ttf?KIWIXCACHEID');
|
||||
}
|
||||
@font-face {
|
||||
font-family:"DM Sans Bold";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src : url('../skin/fonts/DMSans-Regular.ttf?KIWIXCACHEID');
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(to bottom right, #ffffff, #e6e6e6);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
|
||||
margin-top: 15%;
|
||||
margin-bottom: 15%;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 60%;
|
||||
min-width: 200px;
|
||||
max-width: 500px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header, .intro {
|
||||
font-family: "DM Sans";
|
||||
}
|
||||
|
||||
.intro {
|
||||
font-size: 1em;
|
||||
padding: 0 10%;
|
||||
line-height: 1.2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
line-height: 1.1em;
|
||||
font-family: "DM Sans Bold";
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.intro code {
|
||||
font-family: monospace;
|
||||
font-size: 1.1em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.intro a, .intro a:active, .intro a:visited {
|
||||
color: #00b4e4;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.advice {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
margin-bottom: 15%;
|
||||
margin-top: 5em;
|
||||
|
||||
background-color: #ffffff;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid #b7b7b7;
|
||||
|
||||
padding: 2em;
|
||||
|
||||
font-family: "DM Sans";
|
||||
font-size: .9em;
|
||||
box-sizing: border-box;
|
||||
|
||||
align-items: normal;
|
||||
}
|
||||
|
||||
.advice p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.advice p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.advice p.list-intro {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.advice ul {
|
||||
list-style-type: square;
|
||||
margin: 0;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.advice ul li {
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.advice p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
/* sm: 640px+ */
|
||||
@media (width >= 40rem) {
|
||||
header {
|
||||
margin-bottom: 1em;
|
||||
margin-top: 5em;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.intro h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.advice {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* xl: 1280px+ */
|
||||
@media (width >= 80rem) {
|
||||
.intro h1 {
|
||||
font-size: 3.4em;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2xl: 1536px+ */
|
||||
@media (width >= 96rem) {
|
||||
header img {
|
||||
width: 25%;
|
||||
min-width: 200px;
|
||||
max-width: 500px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.advice {
|
||||
width: 25%;
|
||||
min-width: 200px;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
BIN
static/skin/fonts/DMSans-Regular.ttf
Normal file
BIN
static/skin/fonts/DMSans-Regular.ttf
Normal file
Binary file not shown.
@@ -23,7 +23,8 @@ const Translations = {
|
||||
return;
|
||||
|
||||
const errorMsg = `Error loading translations for language '${lang}': `;
|
||||
this.promises[lang] = fetch(`./skin/i18n/${lang}.json`).then(async (resp) => {
|
||||
const translationJsonUrl = import.meta.resolve(`./i18n/${lang}.json`);
|
||||
this.promises[lang] = fetch(translationJsonUrl).then(async (resp) => {
|
||||
if ( resp.ok ) {
|
||||
this.data[lang] = JSON.parse(await resp.text());
|
||||
} else {
|
||||
@@ -190,8 +191,40 @@ function initUILanguageSelector(activeLanguage, languageChangeCallback) {
|
||||
languageSelector.onchange = languageChangeCallback;
|
||||
}
|
||||
|
||||
function parseDom(html) {
|
||||
const domParser = new DOMParser();
|
||||
return domParser.parseFromString(html, "text/html").documentElement;
|
||||
}
|
||||
|
||||
function translatePageInWindow(w) {
|
||||
if ( w.KIWIX_RESPONSE_TEMPLATE && w.KIWIX_RESPONSE_DATA ) {
|
||||
const template = parseDom(w.KIWIX_RESPONSE_TEMPLATE).textContent;
|
||||
|
||||
// w.KIWIX_RESPONSE_DATA may belong to a different context and running
|
||||
// I18n.render() on it directly won't work correctly
|
||||
// because the type checks (obj.__proto__ == ???.prototype) in
|
||||
// I18n.instantiateParameterizedMessages() will fail (String.prototype
|
||||
// refers to different objects in different contexts).
|
||||
// Work arround that issue by copying the object into our context.
|
||||
const params = JSON.parse(JSON.stringify(w.KIWIX_RESPONSE_DATA));
|
||||
|
||||
const newHtml = I18n.render(template, params);
|
||||
w.document.documentElement.innerHTML = parseDom(newHtml).innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
function translateSelf() {
|
||||
if ( window.KIWIX_RESPONSE_TEMPLATE && window.KIWIX_RESPONSE_DATA ) {
|
||||
setUserLanguage(getUserLanguage(), () => {
|
||||
translatePageInWindow(window)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.$t = $t;
|
||||
window.getUserLanguage = getUserLanguage;
|
||||
window.setUserLanguage = setUserLanguage;
|
||||
window.initUILanguageSelector = initUILanguageSelector;
|
||||
window.translatePageInWindow = translatePageInWindow;
|
||||
window.I18n = I18n;
|
||||
window.addEventListener('load', translateSelf);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Asma",
|
||||
"Hamoudak",
|
||||
"Ravan",
|
||||
"محمد أحمد عبد الفتاح"
|
||||
]
|
||||
@@ -12,7 +13,7 @@
|
||||
"no-book-found": "لا يوجد كتاب يطابق معايير الاختيار",
|
||||
"url-not-found": "لم يتم العثور على عنوان URL المطلوب \"{{url}}\" على هذا الخادم.",
|
||||
"suggest-search": "قم بإجراء بحث عن النص الكامل لـ <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "مع الأسف! فشل اختيار مقال عشوائي :(",
|
||||
"random-article-failure": "للأسف ! فشل في اختيار مقال عشوائي :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ليس طلبًا صالحًا للمحتوى الأولي.",
|
||||
"no-value-for-arg": "لم يتم تقديم قيمة للوسيطة {{ARGUMENT}}",
|
||||
"no-query": "لم يتم تقديم ملخص.",
|
||||
@@ -21,13 +22,31 @@
|
||||
"400-page-heading": "طلب غير صالح",
|
||||
"404-page-title": "المحتوى غير موجود",
|
||||
"404-page-heading": "لم يتم العثور عليه",
|
||||
"500-page-title": "خطأ في الخادم الداخلي",
|
||||
"500-page-heading": "خطأ في الخادم الداخلي",
|
||||
"fulltext-search-unavailable": "البحث عن النص الكامل غير متاح",
|
||||
"no-search-results": "محرك البحث عن النص الكامل غير متاح لهذا المحتوى.",
|
||||
"library-button-text": "اذهب لصفحة الترحيب",
|
||||
"500-page-title": "خطأ داخلي بالخادم",
|
||||
"500-page-heading": "خطأ داخلي بالخادم",
|
||||
"fulltext-search-unavailable": "البحث في كامل النص غير متاح",
|
||||
"no-search-results": "محرك البحث في كامل النص غير متوفر لهذا المحتوي.",
|
||||
"library-button-text": "توجه إلي صفحة الترحيب",
|
||||
"home-button-text": "انتقل إلى الصفحة الرئيسية لـ \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "اذهب إلى صفحة عشوائية",
|
||||
"searchbox-tooltip": "بحث \"{{BOOK_TITLE}}\"",
|
||||
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة."
|
||||
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة.",
|
||||
"direct-download-alt-text": "التنزيل مباشرة عبر بروتوكول HTTP(S)",
|
||||
"hash-download-link-text": "المجموع الإختباري لخوارزمية SHA-256",
|
||||
"hash-download-alt-text": "عرض المجموع الإختباري لخوارزمية SHA-256 للملف",
|
||||
"magnet-link-text": "رابط جاذب",
|
||||
"magnet-alt-text": "التنزيل بواسطة الرابط الجاذب",
|
||||
"torrent-download-link-text": "بت تورنت",
|
||||
"torrent-download-alt-text": "التنزيل بواسطة بت تورنت",
|
||||
"unknown-error": "خطأ غير معروف",
|
||||
"book-category.wikibooks": "ويكي الكتب",
|
||||
"book-category.wikinews": "ويكي الأخبار",
|
||||
"book-category.wikipedia": "ويكيبيديا",
|
||||
"book-category.wikiquote": "ويكي الإقتباس",
|
||||
"book-category.wikisource": "ويكي مصدر",
|
||||
"book-category.wikispecies": "ويكي أنواع",
|
||||
"book-category.wikiversity": "ويكي الجامعة",
|
||||
"book-category.wikivoyage": "ويكي الرحلات",
|
||||
"book-category.wiktionary": "ويكي القاموس",
|
||||
"book-category.other": "أخرى {كتب أخري}"
|
||||
}
|
||||
|
||||
@@ -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": "অন্যান্য"
|
||||
}
|
||||
|
||||
@@ -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ŋ"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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>?",
|
||||
|
||||
@@ -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 <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": "Άγνωστο σφάλμα"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "אחר"
|
||||
}
|
||||
|
||||
@@ -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": "अन्य"
|
||||
}
|
||||
|
||||
@@ -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": "Այլ"
|
||||
}
|
||||
|
||||
@@ -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
74
static/skin/i18n/id.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Akmaie Ajam"
|
||||
]
|
||||
},
|
||||
"name": "Bahasa Inggris",
|
||||
"suggest-full-text-search": "mengandung '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Tidak ada buku seperti ini: {{BOOK_NAME}}",
|
||||
"too-many-books": "Terlalu banyak buku yang diminta ({{NB_BOOKS}}) dimana batasnya adalah {{LIMIT}}",
|
||||
"no-book-found": "Tidak ada buku yang sesuai kriteria yang dipilih",
|
||||
"url-not-found": "URL yang diminta \"{{url}}\" tidak ditemukan di server ini.",
|
||||
"suggest-search": "Lakukan pencarian teks lengkap untuk <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Waduh! Gagal memilih artikel acak :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} bukan permintaan yang sah untuk konten mentah.",
|
||||
"invalid-request": "URL yang diminta \"{{{url}}}\" bukan permintaan yang sah.",
|
||||
"no-value-for-arg": "Tidak ada nilai yang diberikan untuk argumen {{ARGUMENT}}",
|
||||
"no-query": "Tidak ada kueri yang diberikan.",
|
||||
"raw-entry-not-found": "Tidak dapat menemukan entri {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Permintaan tidak sah",
|
||||
"400-page-heading": "Permintaan tidak sah",
|
||||
"404-page-title": "Konten tidak ditemukan",
|
||||
"404-page-heading": "Tidak Ditemukan",
|
||||
"500-page-title": "Kesalahan Server Internal",
|
||||
"500-page-heading": "Kesalahan Server Internal",
|
||||
"500-page-text": "Terjadi kesalahan server internal. Kami mohon maaf atas hal ini :/",
|
||||
"fulltext-search-unavailable": "Pencarian teks lengkap tidak tersedia",
|
||||
"no-search-results": "Mesin pencari teks lengkap tidak tersedia untuk konten ini.",
|
||||
"search-results-page-title": "Pencarian: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Hasil <b>{{START}}-{{END}}</b> dari <b>{{COUNT}}</b> untuk <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Tidak ada hasil yang ditemukan untuk <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "dari {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} kata",
|
||||
"library-button-text": "Pergi ke halaman selamat datang",
|
||||
"home-button-text": "Buka halaman utama '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Buka halaman yang dipilih secara acak",
|
||||
"searchbox-tooltip": "Cari '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Dua buku atau lebih dalam bahasa berbeda dapat muncul dalam hasil pencarian, yang dapat memicu hasil yang membingungkan.",
|
||||
"welcome-page-overzealous-filter": "Tidak ada hasil. Apakah Anda ingin <a href=\"{{URL}}\">mengatur ulang filter</a>?",
|
||||
"powered-by-kiwix-html": "Didukung oleh <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Cari",
|
||||
"book-filtering-all-categories": "Semua kategori",
|
||||
"book-filtering-all-languages": "Semua bahasa",
|
||||
"count-of-matching-books": "{{COUNT}} buku",
|
||||
"download": "Unduh",
|
||||
"direct-download-link-text": "Langsung",
|
||||
"direct-download-alt-text": "Unduh langsung melalui HTTP(S)",
|
||||
"hash-download-link-text": "Checksum SHA-256",
|
||||
"hash-download-alt-text": "Tampilkan checksum berkas SHA-256",
|
||||
"magnet-link-text": "Tautan magnet",
|
||||
"magnet-alt-text": "Unduh melalui tautan Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Unduh melalui BitTorrent",
|
||||
"library-opds-feed-all-entries": "Umpan OPDS Perpustakaan - Semua entri",
|
||||
"filter-by-tag": "Saring berdasarkan tag \"{{{TAG}}}\"",
|
||||
"stop-filtering-by-tag": "Berhenti penyaringan berdasarkan tag \"{{{TAG}}}\"",
|
||||
"library-opds-feed-parameterised": "Umpan OPDS Perpustakaan - entri yang cocok dengan {{#LANG}}\nBahasa: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nKueri: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Selamat datang di Server Kiwix",
|
||||
"download-links-heading": "Tautan unduhan untuk <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Unduh buku",
|
||||
"preview-book": "Pratayang",
|
||||
"unknown-error": "Kesalahan yang tidak diketahui",
|
||||
"book-category.wikibooks": "Wikibuku",
|
||||
"book-category.wikinews": "Wikiberita",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikikutip",
|
||||
"book-category.wikisource": "Wikisumber",
|
||||
"book-category.wikispecies": "Wikispesies",
|
||||
"book-category.wikiversity": "Wikiversitas",
|
||||
"book-category.wikivoyage": "Wikiwisata",
|
||||
"book-category.wiktionary": "Wikikamus",
|
||||
"book-category.other": "Lainnya",
|
||||
"text-loading-content": "Memuat Konten"
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"authors": [
|
||||
"Albano",
|
||||
"Beta16",
|
||||
"Clorofolle",
|
||||
"Luca.favorido",
|
||||
"McDutchie"
|
||||
]
|
||||
@@ -21,6 +22,9 @@
|
||||
"400-page-heading": "Richiesta non valida",
|
||||
"404-page-title": "Contenuto non trovato",
|
||||
"404-page-heading": "Non trovato",
|
||||
"new-404-page-title": "Pagina non trovata",
|
||||
"new-404-page-heading": "Oops. Pagina non trovata.",
|
||||
"404-img-text": "Non trovato!",
|
||||
"500-page-title": "Errore interno del server",
|
||||
"500-page-heading": "Errore interno del server",
|
||||
"500-page-text": "Si è verificato un errore interno del server. Ci dispiace :/",
|
||||
@@ -33,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"
|
||||
}
|
||||
|
||||
@@ -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": "기타"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
74
static/skin/i18n/nb.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"TorgeirS"
|
||||
]
|
||||
},
|
||||
"name": "Engelsk",
|
||||
"suggest-full-text-search": "inneholder '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Finner ingen slik bok: {{BOOK_NAME}}",
|
||||
"too-many-books": "Det er forespurt for mange bøker {{NB_BOOKS}} der grensen er {{LIMIT}}",
|
||||
"no-book-found": "Ingen bøker med treff på utvalgkriteriene",
|
||||
"url-not-found": "Den forespurte webadressen «{{url}}» ble ikke funnet på denne serveren.",
|
||||
"suggest-search": "Gjør et fulltekstsøk etter <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Oops! Klarte ikke å hente en tilfeldig artikkel :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} er ikke en gyldig forespørsel etter uformatert innhold.",
|
||||
"invalid-request": "Den forespurte URL-en \"{{{url}}}\" er ikke en gyldig forespørsel.",
|
||||
"no-value-for-arg": "Ingen verdi angitt for argumentet {{ARGUMENT}}",
|
||||
"no-query": "Ingen søkeord oppgitt.",
|
||||
"raw-entry-not-found": "Finner ikke {{DATATYPE}}-oppføringen {{ENTRY}}",
|
||||
"400-page-title": "Ugyldig forespørsel",
|
||||
"400-page-heading": "Ugyldig forespørsel",
|
||||
"404-page-title": "Finner ikke innhold",
|
||||
"404-page-heading": "Ikke funnet",
|
||||
"500-page-title": "Intern serverfeil",
|
||||
"500-page-heading": "Intern serverfeil",
|
||||
"500-page-text": "Det oppstod en intern serverfeil. Vi beklager det :/",
|
||||
"fulltext-search-unavailable": "Fulltekstsøk utilgjengelig",
|
||||
"no-search-results": "Søkemotoren for fulltekstsøk er ikke tilgjengelig for dette innholdet.",
|
||||
"search-results-page-title": "Søk: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultater <b>{{START}}-{{END}}</b> av <b>{{COUNT}}</b> for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Ingen resultater ble funnet for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "fra {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} ord",
|
||||
"library-button-text": "Gå til velkomstsiden",
|
||||
"home-button-text": "Gå til hovedsiden for '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Gå til en tilfeldig valgt side",
|
||||
"searchbox-tooltip": "Søk etter '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "To eller flere bøker på forskjellige språk vil inngå i søket, noe som kan gi forvirrende resultater.",
|
||||
"welcome-page-overzealous-filter": "Ingen resultater. Vil du <a href=\"{{URL}}\">tilbakestille filteret</a>?",
|
||||
"powered-by-kiwix-html": "Drevet av <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Søk",
|
||||
"book-filtering-all-categories": "Alle kategorier",
|
||||
"book-filtering-all-languages": "Alle språk",
|
||||
"count-of-matching-books": "{{COUNT}} bok/bøker",
|
||||
"download": "Last ned",
|
||||
"direct-download-link-text": "Direkte",
|
||||
"direct-download-alt-text": "Last ned direkte via HTTP(S)",
|
||||
"hash-download-link-text": "SHA-256 kontrollsum",
|
||||
"hash-download-alt-text": "Vis filens SHA-256 kontrollsum",
|
||||
"magnet-link-text": "Magnetlenke",
|
||||
"magnet-alt-text": "Last ned via Magnetlenke",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Last ned via BitTorrent",
|
||||
"library-opds-feed-all-entries": "Biblioteks OPDS-feed - Alle oppføringer",
|
||||
"filter-by-tag": "Filtrer etter taggen \"{{{TAG}}}\"",
|
||||
"stop-filtering-by-tag": "Slutt å filtrere etter taggen \"{{{TAG}}}\"",
|
||||
"library-opds-feed-parameterised": "Biblioteks OPDS-feed - oppføringer som samsvarer med {{#LANG}}\nSpråk: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTagg: {{TAG}} {{/TAG}}{{#Q}}\nSpørring: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Velkommen til Kiwix Server",
|
||||
"download-links-heading": "Nedlastingslenker for <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Last ned bok",
|
||||
"preview-book": "Forhåndsvisning",
|
||||
"unknown-error": "Ukjent feil",
|
||||
"book-category.wikibooks": "Wikibooks",
|
||||
"book-category.wikinews": "Wikinews",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversity",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionary",
|
||||
"book-category.other": "Annet",
|
||||
"text-loading-content": "Laster innhold"
|
||||
}
|
||||
@@ -1,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"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Amire80",
|
||||
"Lancine.kounfantoh.fofana"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
75
static/skin/i18n/pt-br.json
Normal file
75
static/skin/i18n/pt-br.json
Normal 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
73
static/skin/i18n/pt.json
Normal 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"
|
||||
}
|
||||
@@ -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
75
static/skin/i18n/ro.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"MSClaudiu",
|
||||
"Trotinel Iftode"
|
||||
]
|
||||
},
|
||||
"name": "Engleză",
|
||||
"suggest-full-text-search": "care conține '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Nu există o astfel de carte: {{BOOK_NAME}}",
|
||||
"too-many-books": "Prea multe cărți solicitate ({{NB_BOOKS}}) unde limita este {{LIMIT}}",
|
||||
"no-book-found": "Nicio carte nu corespunde criteriilor de selecție",
|
||||
"url-not-found": "Adresa URL solicitată \"{{url}}\" nu a fost găsită pe acest server.",
|
||||
"suggest-search": "Efectuați o căutare text complet pentru <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Hopa! Nu s-a putut alege un articol aleatoriu :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} nu este o solicitare validă pentru conținut brut.",
|
||||
"invalid-request": "Adresa URL solicitată \"{{{url}}}\" nu este o solicitare validă.",
|
||||
"no-value-for-arg": "Nu este furnizată nicio valoare pentru argumentul {{ARGUMENT}}",
|
||||
"no-query": "Nu a fost furnizată nicio interogare.",
|
||||
"raw-entry-not-found": "Nu se poate găsi {{DATATYPE}} intrarea {{ENTRY}}",
|
||||
"400-page-title": "Cerere invalidă",
|
||||
"400-page-heading": "Cerere invalidă",
|
||||
"404-page-title": "Conținut nu a fost găsit",
|
||||
"404-page-heading": "Nu a fost găsit",
|
||||
"500-page-title": "Eroare internă de server",
|
||||
"500-page-heading": "Eroare internă de server",
|
||||
"500-page-text": "A apărut o eroare internă de server. Ne pare rau pentru asta :/",
|
||||
"external-link-intro": "Ești pe cale să părăsești cititorul ZIM al Kiwix pentru a te conecta la...",
|
||||
"fulltext-search-unavailable": "Căutarea text integral indisponibilă",
|
||||
"no-search-results": "Motorul de căutare textintegral nu este disponibil pentru acest conținut.",
|
||||
"search-results-page-title": "Căutare: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Rezultatele <b>{{START}}-{{END}}</b> din <b>{{COUNT}}</b> pentru <b>„{{{SEARCH_PATTERN}}}”</b>",
|
||||
"empty-search-results-page-header": "Nu au fost găsite rezultate pentru <b>„{{{SEARCH_PATTERN}}}”</b>",
|
||||
"search-result-book-info": "din {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} cuvinte",
|
||||
"library-button-text": "Mergi la pagina de pornire",
|
||||
"home-button-text": "Accesați pagina principală a „{{BOOK_TITLE}}”",
|
||||
"random-page-button-text": "Accesați o pagină selectată aleatoriu",
|
||||
"searchbox-tooltip": "Căutați „{{BOOK_TITLE}}”",
|
||||
"confusion-of-tongues": "Două sau mai multe cărți în limbi diferite ar participa la căutare, ceea ce poate duce la rezultate confuze.",
|
||||
"welcome-page-overzealous-filter": "Nici un rezultat. Doriți să <a href=\"{{URL}}\">resetați filtrul</a>?",
|
||||
"powered-by-kiwix-html": "Susținut de <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Caută",
|
||||
"book-filtering-all-categories": "Toate categoriile",
|
||||
"book-filtering-all-languages": "Toate limbile",
|
||||
"count-of-matching-books": "{{COUNT}} cărți",
|
||||
"download": "Descărcare",
|
||||
"direct-download-link-text": "Direct",
|
||||
"direct-download-alt-text": "Descărcați direct prin HTTP(S)",
|
||||
"hash-download-link-text": "Sumă de control SHA-256",
|
||||
"hash-download-alt-text": "Afișează suma de verificare a fișierului SHA-256",
|
||||
"magnet-link-text": "Legătură magnetică",
|
||||
"magnet-alt-text": "Descărcați prin linkul Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Descărcați prin BitTorrent",
|
||||
"library-opds-feed-all-entries": "Bibliotecă OPDS Feed - Toate intrările",
|
||||
"filter-by-tag": "Filtrați după eticheta \"{{{TAG}}}\"",
|
||||
"stop-filtering-by-tag": "Opriți filtrarea după eticheta \"{{{TAG}}}\"",
|
||||
"library-opds-feed-parameterised": "Bibliotecă OPDS Feed - intrări care se potrivesc cu {{#LANG}}\nLimba: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtichetă: {{TAG}} {{/TAG}}{{#Q}}\nInterogare: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Bun venit la Kiwix Server",
|
||||
"download-links-heading": "Descărcați linkuri pentru <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Descărcă cartea",
|
||||
"preview-book": "Previzualizare",
|
||||
"unknown-error": "Eroare necunoscută",
|
||||
"book-category.wikibooks": "Wikimanuale",
|
||||
"book-category.wikinews": "Wikiștiri",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikicitat",
|
||||
"book-category.wikisource": "Wikisursă",
|
||||
"book-category.wikispecies": "Wikispecii",
|
||||
"book-category.wikiversity": "Wikiversitate",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wikționar",
|
||||
"book-category.other": "Altul"
|
||||
}
|
||||
@@ -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": "Другое"
|
||||
}
|
||||
|
||||
@@ -38,16 +38,16 @@
|
||||
"count-of-matching-books": "{{COUNT}} libru/os",
|
||||
"download": "Iscàrriga",
|
||||
"direct-download-link-text": "Diretu",
|
||||
"direct-download-alt-text": "iscarrigamentu diretu",
|
||||
"hash-download-link-text": "Hash SHA256",
|
||||
"hash-download-alt-text": "hash de s'iscarrigamentu",
|
||||
"direct-download-alt-text": "Iscàrriga in manera direta tràmite HTTP(S)",
|
||||
"hash-download-link-text": "Summa de controllu SHA-256",
|
||||
"hash-download-alt-text": "Mustra sa summa de controllu SHA-256",
|
||||
"magnet-link-text": "Ligàmene Magnet",
|
||||
"magnet-alt-text": "ligàmene \"magnet\" de iscarrigamentu",
|
||||
"torrent-download-link-text": "Documentu Torrent",
|
||||
"torrent-download-alt-text": "iscàrriga su torrent",
|
||||
"magnet-alt-text": "Iscàrriga impreende su ligàmene Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Iscàrriga impreende BitTorrent",
|
||||
"library-opds-feed-all-entries": "Flussu OPDS de sa biblioteca – Totu sos elementos",
|
||||
"filter-by-tag": "Filtra pro eticheta \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Non filtres prus pro eticheta \"{{TAG}}\"",
|
||||
"filter-by-tag": "Filtra pro eticheta \"{{{TAG}}}\"",
|
||||
"stop-filtering-by-tag": "Non filtres prus pro eticheta \"{{{TAG}}}\"",
|
||||
"library-opds-feed-parameterised": "Flussu OPDS de sa biblioteca - elementos chi currispondet cun {{#LANG}}\nLimba: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEticheta: {{TAG}} {{/TAG}}{{#Q}}\nChirca: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Bene bènnidu a su serbidore de Kiwix",
|
||||
"download-links-heading": "Ligàmenes de iscarrigamentu pro <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"400-page-heading": "غلط ارداس",
|
||||
"404-page-title": "مواد کائنی لبھیا",
|
||||
"404-page-heading": "کائنی لبھا",
|
||||
"new-404-page-title": "ورقہ کائنی لبھیا",
|
||||
"404-img-text": "کائنی لبھا!",
|
||||
"500-page-title": "اندرلا سرور نقص",
|
||||
"500-page-heading": "اندرلا سرور نقص",
|
||||
"search": "ڳولو",
|
||||
@@ -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": "ٻیا"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"suggest-full-text-search": "vsebuje »{{{SEARCH_TERMS}}}« ...",
|
||||
"no-such-book": "Ni take knjige: {{BOOK_NAME}}",
|
||||
"too-many-books": "Preveč zahtevanih knjig ({{NB_BOOKS}}), omejitev je {{LIMIT}}",
|
||||
"no-book-found": "Izbirnim merilom ne ustreza nobena knjiga",
|
||||
"no-book-found": "Izbranim merilom ne ustreza nobena knjiga",
|
||||
"url-not-found": "Zahtevanega URL-ja »{{url}}« v tem strežniku ni bilo mogoče najti.",
|
||||
"suggest-search": "Preiščite celotno besedilo za <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ups! Ni bilo mogoče izbrati naključnega članka :(",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"suggest-search": "Bëni një kërkim të plotë teksti për <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Oh! S’u arrit të merrej një artikull kuturu :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} s’është varg i vlefshëm kërkimi për lëndë të papërpunuar.",
|
||||
"invalid-request": "URL-ja e kërkuar “{{{url}}}” s’është kërkesë e vlefshme.",
|
||||
"no-value-for-arg": "S’u dha vlerë për argumentin {{ARGUMENT}}",
|
||||
"no-query": "S’u dha varg kërkimi.",
|
||||
"raw-entry-not-found": "S’gjendet dot zëri {{DATATYPE}} {{ENTRY}}",
|
||||
@@ -22,8 +23,14 @@
|
||||
"404-page-heading": "S’u Gjet",
|
||||
"500-page-title": "Gabim i Brendshëm Shërbyesi",
|
||||
"500-page-heading": "Gabim i Brendshëm Shërbyesi",
|
||||
"500-page-text": "Ndodhi një gabim i brendshëm shërbyesi. Na ndjeni për këtë :/",
|
||||
"fulltext-search-unavailable": "Kërkim teksti të plotë jo i mundshëm",
|
||||
"no-search-results": "S’është i passhëm motori i kërkimit të tekstit të plotë për këtë lëndë.",
|
||||
"search-results-page-title": "Kërkim: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Përfundime <b>{{START}}-{{END}}</b> nga <b>{{COUNT}}</b> gjithsej për <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "S’u gjetën përfundime për <b>“{{{SEARCH_PATTERN}}}”</b>",
|
||||
"search-result-book-info": "nga {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} fjalë",
|
||||
"library-button-text": "Kalo te faqja e mirëseardhjes",
|
||||
"home-button-text": "Kalo te faqja krye e '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Kalo te një faqe e përzgjedhur kuturu",
|
||||
@@ -37,19 +44,30 @@
|
||||
"count-of-matching-books": "{{COUNT}} libër(a)",
|
||||
"download": "Shkarkoje",
|
||||
"direct-download-link-text": "Drejtpërsëdrejti",
|
||||
"direct-download-alt-text": "shkarkim i drejtpërdrejt",
|
||||
"hash-download-link-text": "Hash sha256",
|
||||
"hash-download-alt-text": "shkarko hashin",
|
||||
"direct-download-alt-text": "Shkarkoje drejtpërsëdrejti përmes HTTP(S)",
|
||||
"hash-download-link-text": "“Checksum” SHA-256",
|
||||
"hash-download-alt-text": "Shfaq “checksum” SHA-256 kartele",
|
||||
"magnet-link-text": "Lidhje Magnet",
|
||||
"magnet-alt-text": "shkarko magnetin",
|
||||
"torrent-download-link-text": "Kartelë Torrent",
|
||||
"torrent-download-alt-text": "shkarko torrent-in",
|
||||
"magnet-alt-text": "Shkarkoje përmes lidhjeje Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Shkarkoje përmes BitTorrent-it",
|
||||
"library-opds-feed-all-entries": "Prurje OPDS Biblioteke - Krejt zërat",
|
||||
"filter-by-tag": "Filtroji sipas etiketës “{{TAG}}”",
|
||||
"stop-filtering-by-tag": "Resht së filtruari sipas etiketë “{{TAG}}”",
|
||||
"filter-by-tag": "Filtroji sipas etikete “{{{TAG}}}”",
|
||||
"stop-filtering-by-tag": "Resht së filtruari sipas etikete “{{{TAG}}}”",
|
||||
"library-opds-feed-parameterised": "Prurje OPDS Biblioteke - zëra që kanë përputhje me {{#LANG}}\nGjuhë: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtiketë: {{TAG}} {{/TAG}}{{#Q}}\nVarg Kërkimi: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Mirë se vini në Shërbyesin Kiwix",
|
||||
"download-links-heading": "Lidhje shkarkimi për <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Shkarkoje librin",
|
||||
"preview-book": "Paraparje"
|
||||
"preview-book": "Paraparje",
|
||||
"unknown-error": "Gabim i panjohur",
|
||||
"book-category.wikibooks": "Wikibooks",
|
||||
"book-category.wikinews": "Wikinews",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversity",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionary",
|
||||
"book-category.other": "Tjetër"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Jopparn",
|
||||
"Larsa",
|
||||
"Rofiatmustapha12",
|
||||
"Sabelöga",
|
||||
"WikiPhoenix"
|
||||
@@ -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
67
static/skin/i18n/sw.json
Normal 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"
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Amire80",
|
||||
"Chaduvari",
|
||||
"Rishitha 1238",
|
||||
"V Bhavya"
|
||||
|
||||
@@ -13,6 +13,21 @@
|
||||
, "400-page-heading": "[I18N TESTING] -400 karma for an invalid request"
|
||||
, "404-page-title": "[I18N TESTING] Not Found - Try Again"
|
||||
, "404-page-heading": "[I18N TESTING] Content not found, but at least the server is alive"
|
||||
, "new-404-page-title" : "Page [I18N] not [TESTING] found"
|
||||
, "new-404-page-heading" : "[I18N TESTING] Oops. Larry Page could not be reached. He may be on paternity leave."
|
||||
, "404-img-text": "[I18N] Not found! [TESTING]"
|
||||
, "path-was-not-found": "[I18N TESTING] The requested path was not found (in fact, nothing was found instead, either):"
|
||||
, "404-advice.p1": "Sh*t happens. [I18N TESTING] Take it easy!"
|
||||
, "404-advice.p2": "[I18N TESTING] Try one of the following:"
|
||||
, "404-advice.p3": "[I18N TESTING] Check the spelling of the URL path"
|
||||
, "404-advice.p4": "[I18N TESTING] Press the dice button"
|
||||
, "404-advice.p5": "Good luck! [I18N TESTING]"
|
||||
, "external-link-detected" : "External [I18] Link [TESTING] Detected"
|
||||
, "caution-warning" : "[I18N] C5n! [TESTING]"
|
||||
, "external-link-intro" : "[I18N TESTING] The following link may lead you to a place from which you won't ever be able to return"
|
||||
, "external-link-advice.p1": "[I18N TESTING] The link you're trying to access points past the end of the ZIM file."
|
||||
, "external-link-advice.p2": "[I18N TESTING] If you are an optimist, you can attempt to open the link."
|
||||
, "external-link-advice.p3": "[I18N TESTING] But we strongly recommend you to be reasonable and press your browser's back button."
|
||||
, "library-button-text": "[I18N TESTING] Navigate to the welcome page"
|
||||
, "home-button-text": "[I18N TESTING] Jump to the main page of '{{BOOK_TITLE}}'"
|
||||
, "random-page-button-text": "[I18N TESTING] I am tired of determinism"
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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": "内容加载中"
|
||||
}
|
||||
|
||||
@@ -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": "直接透過 HTTP(S)下載",
|
||||
"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": "正在載入內容"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,6 +15,35 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 24px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
white-space: nowrap; /* Prevents text from wrapping */
|
||||
min-width: fit-content; /* Ensures the container doesn’t shrink */
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #ccc;
|
||||
border-top: 3px solid #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #00b4e4;
|
||||
color: white;
|
||||
@@ -96,6 +125,11 @@ body {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "poppins";
|
||||
src: url("../skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");
|
||||
|
||||
@@ -2,12 +2,12 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ar",
|
||||
"self_name": "الإنجليزية",
|
||||
"translation_count": 25
|
||||
"translation_count": 43
|
||||
},
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 14
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -38,7 +38,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 160px;
|
||||
max-width: 30ch;
|
||||
}
|
||||
|
||||
.kiwix .kiwix_centered {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
35
static/templates/sexy404.html
Normal file
35
static/templates/sexy404.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>{{PAGE_TITLE}}</title>
|
||||
<link type="text/css" href="{{root}}/skin/error.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
{{#KIWIX_RESPONSE_DATA}} <script>
|
||||
window.KIWIX_RESPONSE_TEMPLATE = "{{KIWIX_RESPONSE_TEMPLATE}}";
|
||||
window.KIWIX_RESPONSE_DATA = {{{KIWIX_RESPONSE_DATA}}};
|
||||
</script>{{/KIWIX_RESPONSE_DATA}}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="{{root}}/skin/404.svg?KIWIXCACHEID"
|
||||
alt="{{404_img_text}}"
|
||||
aria-label="{{404_img_text}}"
|
||||
title="{{404_img_text}}">
|
||||
</header>
|
||||
<section class="intro">
|
||||
<h1>{{PAGE_HEADING}}</h1>
|
||||
<p>{{path_was_not_found_msg}}</p>
|
||||
<p><code>{{url_path}}</code></p>
|
||||
</section>
|
||||
<section class="advice">
|
||||
<p>{{advice.p1}}</p>
|
||||
<p class="list-intro">{{advice.p2}}</p>
|
||||
<ul>
|
||||
<li>{{advice.p3}}</li>
|
||||
<li>{{advice.p4}}</li>
|
||||
</ul>
|
||||
<p>{{advice.p5}}</p>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,6 +33,10 @@
|
||||
</head>
|
||||
|
||||
<body style="margin:0" onload="setupViewer()">
|
||||
<div class="loader" id="kiwix__loader">
|
||||
<div class="spinner"></div>
|
||||
<span id="kiwix__loading_text">Loading content...</span>
|
||||
</div>
|
||||
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
|
||||
<div id="kiwixtoolbar" class="ui-widget-header">
|
||||
<div class="kiwix_centered">
|
||||
@@ -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>🏠</button></a>
|
||||
<a id="kiwix_serve_taskbar_library_button"
|
||||
title="Go to welcome page"
|
||||
accesskey="w"
|
||||
aria-label="Go to welcome page"
|
||||
href="./">
|
||||
<button>🏠</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>🎲</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%"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user