mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-08-07 02:53:58 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
035667b0e1 | ||
|
|
d2b014656b |
No files matched your search
+70
-132
@@ -8,27 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macOS:
|
macOS:
|
||||||
strategy:
|
runs-on: macos-12
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os:
|
|
||||||
- macos-13
|
|
||||||
target:
|
|
||||||
- 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:
|
env:
|
||||||
HOME: /Users/runner
|
HOME: /Users/runner
|
||||||
steps:
|
steps:
|
||||||
@@ -39,165 +19,123 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
brew update
|
brew update
|
||||||
brew install pkg-config ninja meson
|
brew install pkg-config ninja meson
|
||||||
env:
|
|
||||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
|
||||||
with:
|
|
||||||
target_platform: ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
env:
|
env:
|
||||||
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig
|
ARCHIVE_NAME: deps2_osx_native_dyn_libkiwix.tar.xz
|
||||||
CPPFLAGS: -I${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/include
|
|
||||||
MESON_OPTION: --default-library=shared -Db_coverage=true
|
|
||||||
MESON_CROSSFILE: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt
|
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
if [ -e $MESON_CROSSFILE ]; then
|
wget -O- https://tmp.kiwix.org/ci/${{env.ARCHIVE_NAME}} | tar -xJ -C ${{env.HOME}}
|
||||||
MESON_OPTION="$MESON_OPTION --cross-file $MESON_CROSSFILE -Dstatic-linkage=true"
|
|
||||||
fi
|
- name: Compile source code
|
||||||
meson . build ${MESON_OPTION}
|
env:
|
||||||
|
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_native_dyn/INSTALL/lib/pkgconfig
|
||||||
|
CPPFLAGS: -I${{env.HOME}}/BUILD_native_dyn/INSTALL/include
|
||||||
|
run: |
|
||||||
|
meson . build --default-library=shared -Db_coverage=true
|
||||||
ninja -C build
|
ninja -C build
|
||||||
|
|
||||||
- name: Test libkiwix
|
- name: Test libkiwix
|
||||||
if: matrix.run_test
|
|
||||||
env:
|
env:
|
||||||
SKIP_BIG_MEMORY_TEST: 1
|
SKIP_BIG_MEMORY_TEST: 1
|
||||||
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib:${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib64
|
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_native_dyn/INSTALL/lib:${{env.HOME}}/BUILD_native_dyn/INSTALL/lib64
|
||||||
run: meson test -C build --verbose
|
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=debug
|
|
||||||
cd build
|
|
||||||
ninja.exe
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
shell: cmd
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
meson.exe test --verbose
|
|
||||||
env:
|
|
||||||
WAIT_TIME_FACTOR_TEST: 10
|
|
||||||
|
|
||||||
Linux:
|
Linux:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
name:
|
||||||
- linux-x86_64-static
|
- native_static
|
||||||
- linux-x86_64-dyn
|
- native_dyn
|
||||||
- android-arm
|
- android_arm
|
||||||
- android-arm64
|
- android_arm64
|
||||||
|
- win32_static
|
||||||
|
- win32_dyn
|
||||||
include:
|
include:
|
||||||
- target: linux-x86_64-static
|
- name: native_static
|
||||||
image_variant: focal
|
target: native_static
|
||||||
|
image_variant: bionic
|
||||||
lib_postfix: '/x86_64-linux-gnu'
|
lib_postfix: '/x86_64-linux-gnu'
|
||||||
arch_name: linux-x86_64
|
- name: native_dyn
|
||||||
run_test: true
|
target: native_dyn
|
||||||
coverage: true
|
image_variant: bionic
|
||||||
- target: linux-x86_64-dyn
|
|
||||||
image_variant: focal
|
|
||||||
lib_postfix: '/x86_64-linux-gnu'
|
lib_postfix: '/x86_64-linux-gnu'
|
||||||
arch_name: linux-x86_64
|
- name: android_arm
|
||||||
run_test: true
|
target: android_arm
|
||||||
coverage: true
|
image_variant: bionic
|
||||||
- target: android-arm
|
|
||||||
image_variant: focal
|
|
||||||
lib_postfix: '/arm-linux-androideabi'
|
lib_postfix: '/arm-linux-androideabi'
|
||||||
arch_name: arm-linux-androideabi
|
- name: android_arm64
|
||||||
run_test: false
|
target: android_arm64
|
||||||
coverage: false
|
image_variant: bionic
|
||||||
- target: android-arm64
|
|
||||||
image_variant: focal
|
|
||||||
lib_postfix: '/aarch64-linux-android'
|
lib_postfix: '/aarch64-linux-android'
|
||||||
arch_name: aarch64-linux-android
|
- name: win32_static
|
||||||
run_test: false
|
target: win32_static
|
||||||
coverage: false
|
image_variant: f35
|
||||||
|
lib_postfix: '64'
|
||||||
|
- name: win32_dyn
|
||||||
|
target: win32_dyn
|
||||||
|
image_variant: f35
|
||||||
|
lib_postfix: '64'
|
||||||
env:
|
env:
|
||||||
HOME: /home/runner
|
HOME: /home/runner
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:38"
|
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:36"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
shell: python
|
||||||
- name: Install dependencies
|
run: |
|
||||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
from subprocess import check_call
|
||||||
with:
|
from os import environ
|
||||||
target_platform: ${{ matrix.target }}
|
command = [
|
||||||
|
'git', 'clone',
|
||||||
|
'https://github.com/${{github.repository}}',
|
||||||
|
'--depth=1',
|
||||||
|
'--branch', '${{ github.head_ref || github.ref_name }}'
|
||||||
|
]
|
||||||
|
check_call(command, cwd=environ['HOME'])
|
||||||
|
- name: Install deps
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ARCHIVE_NAME=deps2_${OS_NAME}_${{matrix.target}}_libkiwix.tar.xz
|
||||||
|
wget -O- http://tmp.kiwix.org/ci/${ARCHIVE_NAME} | tar -xJ -C /home/runner
|
||||||
- name: Compile
|
- name: Compile
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
meson --version
|
meson --version
|
||||||
if [[ "${{matrix.target}}" =~ .*-dyn ]]; then
|
if [[ "${{matrix.target}}" =~ .*_dyn ]]; then
|
||||||
MESON_OPTION="--default-library=shared"
|
MESON_OPTION="--default-library=shared"
|
||||||
else
|
else
|
||||||
MESON_OPTION="--default-library=static"
|
MESON_OPTION="--default-library=static"
|
||||||
fi
|
fi
|
||||||
if [ -e "${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt" ]; then
|
if [[ "${{matrix.target}}" =~ native_.* ]]; then
|
||||||
MESON_OPTION="$MESON_OPTION --cross-file ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt"
|
|
||||||
else
|
|
||||||
MESON_OPTION="$MESON_OPTION -Db_coverage=true"
|
MESON_OPTION="$MESON_OPTION -Db_coverage=true"
|
||||||
|
else
|
||||||
|
MESON_OPTION="$MESON_OPTION --cross-file $HOME/BUILD_${{matrix.target}}/meson_cross_file.txt"
|
||||||
fi
|
fi
|
||||||
if [[ "${{matrix.target}}" =~ android-.* ]]; then
|
if [[ "${{matrix.target}}" =~ android_.* ]]; then
|
||||||
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
|
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
|
||||||
fi
|
fi
|
||||||
|
cd $HOME/libkiwix
|
||||||
meson . build ${MESON_OPTION}
|
meson . build ${MESON_OPTION}
|
||||||
cd build
|
cd build
|
||||||
ninja
|
ninja
|
||||||
env:
|
env:
|
||||||
PKG_CONFIG_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}/pkgconfig"
|
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.arch_name}}/INSTALL/include"
|
CPPFLAGS: "-I/home/runner/BUILD_${{matrix.target}}/INSTALL/include"
|
||||||
- name: Test
|
- name: Test
|
||||||
if: matrix.run_test
|
if: startsWith(matrix.target, 'native_')
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cd build
|
cd $HOME/libkiwix/build
|
||||||
meson test --verbose
|
meson test --verbose
|
||||||
if [[ "${{matrix.coverage}}" = "true" ]]; then
|
ninja coverage
|
||||||
ninja coverage
|
|
||||||
fi
|
|
||||||
env:
|
env:
|
||||||
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}"
|
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.target}}/INSTALL/lib:/home/runner/BUILD_${{matrix.target}}/INSTALL/lib${{matrix.lib_postfix}}"
|
||||||
SKIP_BIG_MEMORY_TEST: 1
|
SKIP_BIG_MEMORY_TEST: 1
|
||||||
|
|
||||||
- name: Publish coverage
|
- name: Publish coverage
|
||||||
if: matrix.coverage
|
if: startsWith(matrix.target, 'native_')
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@@ -5,26 +5,20 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-deb:
|
build-deb:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
distro:
|
distro:
|
||||||
# - debian-unstable
|
- ubuntu-kinetic
|
||||||
# - debian-trixie
|
|
||||||
# - debian-bookworm
|
|
||||||
# - debian-bullseye
|
|
||||||
- ubuntu-noble
|
|
||||||
- ubuntu-jammy
|
- ubuntu-jammy
|
||||||
- ubuntu-focal
|
- ubuntu-focal
|
||||||
|
- ubuntu-bionic
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
# Determine which PPA we should upload to
|
# Determine which PPA we should upload to
|
||||||
- name: PPA
|
- name: PPA
|
||||||
@@ -39,44 +33,16 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
|
|
||||||
- uses: legoktm/gh-action-auto-dch@main
|
- uses: legoktm/gh-action-auto-dch@master
|
||||||
with:
|
with:
|
||||||
fullname: Kiwix builder
|
fullname: Kiwix builder
|
||||||
email: release+launchpad@kiwix.org
|
email: release+launchpad@kiwix.org
|
||||||
distro: ${{ matrix.distro }}
|
distro: ${{ matrix.distro }}
|
||||||
|
|
||||||
# - uses: legoktm/gh-action-build-deb@debian-unstable
|
- uses: legoktm/gh-action-build-deb@ubuntu-kinetic
|
||||||
# if: matrix.distro == 'debian-unstable'
|
if: matrix.distro == 'ubuntu-kinetic'
|
||||||
# name: Build package for debian-unstable
|
name: Build package for ubuntu-kinetic
|
||||||
# id: build-debian-unstable
|
id: build-ubuntu-kinetic
|
||||||
# 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:
|
with:
|
||||||
args: --no-sign
|
args: --no-sign
|
||||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||||
@@ -97,24 +63,34 @@ jobs:
|
|||||||
args: --no-sign
|
args: --no-sign
|
||||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||||
|
|
||||||
|
- uses: legoktm/gh-action-build-deb@ubuntu-bionic
|
||||||
|
if: matrix.distro == 'ubuntu-bionic'
|
||||||
|
name: Build package for ubuntu-bionic
|
||||||
|
id: build-ubuntu-bionic
|
||||||
|
with:
|
||||||
|
args: --no-sign
|
||||||
|
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Packages for ${{ matrix.distro }}
|
name: Packages for ${{ matrix.distro }}
|
||||||
path: output
|
path: output
|
||||||
|
|
||||||
- uses: legoktm/gh-action-dput@main
|
- uses: legoktm/gh-action-dput@master
|
||||||
name: Upload dev package
|
name: Upload dev package
|
||||||
# Only upload on pushes to main
|
# Only upload on pushes to git default branch
|
||||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
|
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
|
||||||
with:
|
with:
|
||||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||||
repository: ppa:kiwixteam/dev
|
repository: ppa:kiwixteam/dev
|
||||||
packages: output/*_source.changes
|
packages: output/*_source.changes
|
||||||
|
|
||||||
- uses: legoktm/gh-action-dput@main
|
- uses: legoktm/gh-action-dput@master
|
||||||
name: Upload release package
|
name: Upload release package
|
||||||
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
|
# Only upload on pushes to master or tag
|
||||||
|
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && startswith(matrix.distro, 'ubuntu-')
|
||||||
with:
|
with:
|
||||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||||
repository: ppa:kiwixteam/release
|
repository: ppa:kiwixteam/release
|
||||||
packages: output/*_source.changes
|
packages: output/*_source.changes
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Read the Docs configuration file
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
# Required
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
# Set the version of Python and other tools you might need
|
|
||||||
build:
|
|
||||||
os: ubuntu-22.04
|
|
||||||
tools:
|
|
||||||
python: "3.11"
|
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
|
||||||
sphinx:
|
|
||||||
configuration: docs/conf.py
|
|
||||||
|
|
||||||
# We recommend specifying your dependencies to enable reproducible builds:
|
|
||||||
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
|
||||||
python:
|
|
||||||
install:
|
|
||||||
- requirements: docs/requirements.txt
|
|
||||||
@@ -1,97 +1,3 @@
|
|||||||
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
|
|
||||||
===============
|
|
||||||
|
|
||||||
* Server:
|
|
||||||
- Properly translated error pages (@veloman-yunkan #1032)
|
|
||||||
- Properly translated search result page (@veloman-yunkan #1046)
|
|
||||||
- Default UI language is resolved in frontend (@veloman-yunkan #1044)
|
|
||||||
- Better support of older Web browsers by polyfilling replaceAll() (@veloman-yunkan #1054)
|
|
||||||
* New API to migrate bookmarks between books (@mgautierfr #1043)
|
|
||||||
* Fixed compilation on Haiku OS (@Begasus #1048)
|
|
||||||
|
|
||||||
libkiwix 13.0.0
|
|
||||||
===============
|
|
||||||
|
|
||||||
* Server:
|
|
||||||
- Improved look & feel of kiwix-serve UI (@veloman-yunkan #917 #1021)
|
|
||||||
- Increase tolerance to malformed (control characters) ZIM entry titles (@veloman-yunkan #1023)
|
|
||||||
- API allowing to filter many categories at once (@juuz0 #974)
|
|
||||||
- Cookie-less user language control (@veloman-yumkan #997)
|
|
||||||
- Hack to fix Mirrorbrain based broken magnet URLs (@rgaudin #1001)
|
|
||||||
* Fix handling of books with 'Name' metadata with dots (@mgautier #1016)
|
|
||||||
* New method beautifyFileSize() to provide nice-looking book sizes (@vuuz0 #971)
|
|
||||||
* Fix a few missing includes (@mgautierfr #978)
|
|
||||||
* New functions to read - kiwix-serve - languages and categories streams (@juuz0 #967)
|
|
||||||
* Add support of Fon language (@kelson42 #1013)
|
|
||||||
* C++17 code base compliancy (@mgautierfr #996)
|
|
||||||
* Use everywhere std::shared_ptr in place of raw pointer (@mgautierfr #991)
|
|
||||||
* Do not use [[nodiscard]] attribute on compiler not supporting it (@mgautierfr #1003)
|
|
||||||
* Add a non minified version of autoComplete.js (@mgautierfr #1008)
|
|
||||||
* Multiple CI/CD improvements (@kelson42 #982)
|
|
||||||
|
|
||||||
libkiwix 12.1.0
|
|
||||||
===============
|
|
||||||
|
|
||||||
* Server:
|
|
||||||
- Introduce a `/nojs` endpoint to browse catalog and zim files with a browser without js (@juuz0 #897)
|
|
||||||
- Translate the viewer (@veloman-yunkan #871 #846)
|
|
||||||
- Display `mul` on tile when zim is multi-languages (@juuz0 #934)
|
|
||||||
- Suggestion links point to the `/content` endpoint (@veloman-yunkan #862)
|
|
||||||
- Correctly compress web fonts in http answers (@kelson42 #856)
|
|
||||||
- Correctly encode link in suggestions (@veloman-yunkan #859 #860 #963)
|
|
||||||
- Correctly encode url redirection (@veloman-yunkan #866 #890)
|
|
||||||
- Properly handle user language, through cookies and http headers (@veloman-yunkan #849 #869)
|
|
||||||
- Fix url encoding (@veloman-yunkan #870)
|
|
||||||
- Fix viewer for viewer for SeaMonkey (@veloman-yunkan #887)
|
|
||||||
- Make the downloader threadsafe (@mgautierfr #886)
|
|
||||||
- Add RSS feed in the main page (pointing to the catalog) (@juuz0 #882 #920)
|
|
||||||
- Correctly set the mimetype for json and ico (@veloman-yunkan #892)
|
|
||||||
- `count=-1` correspond to unlimited count (instead of 0) (@veloman-yunkan #894)
|
|
||||||
- Keep the navigation bar on top (@juuz0 #896)
|
|
||||||
- Make the viewer's iframe "safe" (@veloman-yunkan #906 #930)
|
|
||||||
- Correctly escape search link in XML Opds output (@veloman-yunkan #936)
|
|
||||||
- Store values needed for the viewer js in the url fragment instead of the query string (@juuz0 #907)
|
|
||||||
- Get rid of legacy OPDS API usage in the viewer (@veloman-yunkan #939)
|
|
||||||
- Fix charset encoding declaration in OPDS response MIME types (@veloman-yunkan #942)
|
|
||||||
- Fix PDF in the viewer (@veloman-yunkan #940)
|
|
||||||
- Fix external links handling in the viewer (@veloman-yunkan #959)
|
|
||||||
- Add tests of searching with accents (@mgautierfs #954)
|
|
||||||
* Fix handling of missing illustration in the book (@veloman-yunkan #961)
|
|
||||||
* Add support for multi languages zim files (@veloman-yunkan #904)
|
|
||||||
* Fix includes for openbsd (@bentley #949)
|
|
||||||
* Fix pathes in git to allow git clone on Windows (@adamlamar #868)
|
|
||||||
* Switch to `main` as principal branch (instead of `master`) (@kelson42)
|
|
||||||
* Remove libkiwix android publisher from the repository (@kelson42 #884)
|
|
||||||
* Various fixes of meson and CI. (@mgautierfr @kelson42)
|
|
||||||
|
|
||||||
libkiwix 12.0.0
|
libkiwix 12.0.0
|
||||||
===============
|
===============
|
||||||
|
|
||||||
@@ -131,6 +37,7 @@ libkiwix 12.0.0
|
|||||||
* Fix documentation (@kelson42 #816)
|
* Fix documentation (@kelson42 #816)
|
||||||
* Udpate translation (#787 #839 #847)
|
* Udpate translation (#787 #839 #847)
|
||||||
|
|
||||||
|
|
||||||
libkiwix 11.0.0
|
libkiwix 11.0.0
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ with the Libkiwix compilation itself, we recommend to have a look to
|
|||||||
Preamble
|
Preamble
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Although the Libkiwix can be (cross-)compiled on/for many systems, the
|
Although the Libkiwix can be (cross-)compiled on/for many sytems, the
|
||||||
following documentation explains how to do it on POSIX ones. It is
|
following documentation explains how to do it on POSIX ones. It is
|
||||||
primarily thought for GNU/Linux systems and has been tested on recent
|
primarly thought for GNU/Linux systems and has been tested on recent
|
||||||
releases of Ubuntu and Fedora.
|
releases of Ubuntu and Fedora.
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
@@ -54,7 +54,7 @@ The following dependency needs to be available at runtime:
|
|||||||
These dependencies may or may not be packaged by your operating
|
These dependencies may or may not be packaged by your operating
|
||||||
system. They may also be packaged but only in an older version. The
|
system. They may also be packaged but only in an older version. The
|
||||||
compilation script will tell you if one of them is missing or too old.
|
compilation script will tell you if one of them is missing or too old.
|
||||||
In the worst case, you will have to download and compile bleeding edge
|
In the worse case, you will have to download and compile bleeding edge
|
||||||
version by hand.
|
version by hand.
|
||||||
|
|
||||||
If you want to install these dependencies locally, then use the
|
If you want to install these dependencies locally, then use the
|
||||||
@@ -201,7 +201,7 @@ To use JS provided by kiwix-serve you can use the following template to start wi
|
|||||||
|
|
||||||
|
|
||||||
If you compile manually Libmicrohttpd, you might need to compile it
|
If you compile manually Libmicrohttpd, you might need to compile it
|
||||||
without GNU TLS, a bug here will impeach further compilation
|
without GNU TLS, a bug here will empeach further compilation
|
||||||
otherwise.
|
otherwise.
|
||||||
|
|
||||||
If the compilation still fails, you might need to get a more recent
|
If the compilation still fails, you might need to get a more recent
|
||||||
|
|||||||
Vendored
+8
-9
@@ -3,12 +3,13 @@ Priority: optional
|
|||||||
Maintainer: Kiwix team <kiwix@kiwix.org>
|
Maintainer: Kiwix team <kiwix@kiwix.org>
|
||||||
Build-Depends: debhelper-compat (= 13),
|
Build-Depends: debhelper-compat (= 13),
|
||||||
meson,
|
meson,
|
||||||
pkgconf,
|
pkg-config,
|
||||||
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
|
libzim-dev (>= 7.2.0~),
|
||||||
libcurl4-gnutls-dev,
|
libcurl4-gnutls-dev,
|
||||||
libicu-dev,
|
libicu-dev,
|
||||||
libgtest-dev,
|
libgtest-dev,
|
||||||
libkainjow-mustache-dev,
|
libkainjow-mustache-dev,
|
||||||
|
liblzma-dev,
|
||||||
libmicrohttpd-dev,
|
libmicrohttpd-dev,
|
||||||
libpugixml-dev,
|
libpugixml-dev,
|
||||||
zlib1g-dev
|
zlib1g-dev
|
||||||
@@ -21,13 +22,12 @@ Package: libkiwix-dev
|
|||||||
Section: libdevel
|
Section: libdevel
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Multi-Arch: same
|
Multi-Arch: same
|
||||||
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
|
Depends: libkiwix10 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||||
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
|
libzim-dev (>= 7.2.0~),
|
||||||
libicu-dev,
|
libicu-dev,
|
||||||
libpugixml-dev,
|
libpugixml-dev,
|
||||||
libcurl4-gnutls-dev,
|
libcurl4-gnutls-dev,
|
||||||
libmicrohttpd-dev,
|
libmicrohttpd-dev
|
||||||
zlib1g-dev
|
|
||||||
Description: library of common code for Kiwix (development)
|
Description: library of common code for Kiwix (development)
|
||||||
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
||||||
software core for Kiwix, and contains the code shared by all
|
software core for Kiwix, and contains the code shared by all
|
||||||
@@ -35,12 +35,11 @@ Description: library of common code for Kiwix (development)
|
|||||||
.
|
.
|
||||||
This package contains development files.
|
This package contains development files.
|
||||||
|
|
||||||
Package: libkiwix14
|
Package: libkiwix10
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Multi-Arch: same
|
Multi-Arch: same
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
|
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
|
||||||
Conflicts: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
Conflicts: libkiwix0, libkiwix3, libkiwix9
|
||||||
Replaces: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
|
||||||
Description: library of common code for Kiwix
|
Description: library of common code for Kiwix
|
||||||
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
||||||
software core for Kiwix, and contains the code shared by all
|
software core for Kiwix, and contains the code shared by all
|
||||||
|
|||||||
File renamed without changes.
+5
-1
@@ -24,6 +24,8 @@ author = 'libkiwix-team'
|
|||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
@@ -40,7 +42,9 @@ templates_path = ['_templates']
|
|||||||
# This pattern also affects html_static_path and html_extra_path.
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
html_theme = 'sphinx_rtd_theme'
|
|
||||||
|
if not on_rtd:
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
breathe
|
breathe
|
||||||
exhale
|
exhale
|
||||||
sphinx_rtd_theme
|
|
||||||
@@ -29,33 +29,19 @@ class xml_node;
|
|||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
|
|
||||||
class Book;
|
|
||||||
/**
|
/**
|
||||||
* A class to store information about a bookmark (an article in a book)
|
* A class to store information about a bookmark (an article in a book)
|
||||||
*/
|
*/
|
||||||
class Bookmark
|
class Bookmark
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* Create an empty bookmark.
|
|
||||||
*
|
|
||||||
* Bookmark must be populated with `set*` methods
|
|
||||||
*/
|
|
||||||
Bookmark();
|
Bookmark();
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a bookmark given a Book, a path and a title.
|
|
||||||
*/
|
|
||||||
Bookmark(const Book& book, const std::string& path, const std::string& title);
|
|
||||||
|
|
||||||
~Bookmark();
|
~Bookmark();
|
||||||
|
|
||||||
void updateFromXml(const pugi::xml_node& node);
|
void updateFromXml(const pugi::xml_node& node);
|
||||||
|
|
||||||
const std::string& getBookId() const { return m_bookId; }
|
const std::string& getBookId() const { return m_bookId; }
|
||||||
const std::string& getBookTitle() const { return m_bookTitle; }
|
const std::string& getBookTitle() const { return m_bookTitle; }
|
||||||
const std::string& getBookName() const { return m_bookName; }
|
|
||||||
const std::string& getBookFlavour() const { return m_bookFlavour; }
|
|
||||||
const std::string& getUrl() const { return m_url; }
|
const std::string& getUrl() const { return m_url; }
|
||||||
const std::string& getTitle() const { return m_title; }
|
const std::string& getTitle() const { return m_title; }
|
||||||
const std::string& getLanguage() const { return m_language; }
|
const std::string& getLanguage() const { return m_language; }
|
||||||
@@ -63,8 +49,6 @@ class Bookmark
|
|||||||
|
|
||||||
void setBookId(const std::string& bookId) { m_bookId = bookId; }
|
void setBookId(const std::string& bookId) { m_bookId = bookId; }
|
||||||
void setBookTitle(const std::string& bookTitle) { m_bookTitle = bookTitle; }
|
void setBookTitle(const std::string& bookTitle) { m_bookTitle = bookTitle; }
|
||||||
void setBookName(const std::string& bookName) { m_bookName = bookName; }
|
|
||||||
void setBookFlavour(const std::string& bookFlavour) { m_bookFlavour = bookFlavour; }
|
|
||||||
void setUrl(const std::string& url) { m_url = url; }
|
void setUrl(const std::string& url) { m_url = url; }
|
||||||
void setTitle(const std::string& title) { m_title = title; }
|
void setTitle(const std::string& title) { m_title = title; }
|
||||||
void setLanguage(const std::string& language) { m_language = language; }
|
void setLanguage(const std::string& language) { m_language = language; }
|
||||||
@@ -73,8 +57,6 @@ class Bookmark
|
|||||||
protected:
|
protected:
|
||||||
std::string m_bookId;
|
std::string m_bookId;
|
||||||
std::string m_bookTitle;
|
std::string m_bookTitle;
|
||||||
std::string m_bookName;
|
|
||||||
std::string m_bookFlavour;
|
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
std::string m_title;
|
std::string m_title;
|
||||||
std::string m_language;
|
std::string m_language;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
|
|
||||||
enum class IpMode { IPV4, IPV6, ALL, AUTO }; // AUTO: Server decides the protocol
|
|
||||||
typedef zim::size_type size_type;
|
typedef zim::size_type size_type;
|
||||||
typedef zim::offset_type offset_type;
|
typedef zim::offset_type offset_type;
|
||||||
|
|
||||||
|
|||||||
+5
-21
@@ -168,16 +168,8 @@ class Download {
|
|||||||
*/
|
*/
|
||||||
class Downloader
|
class Downloader
|
||||||
{
|
{
|
||||||
public: // types
|
public:
|
||||||
typedef std::vector<std::pair<std::string, std::string>> Options;
|
Downloader();
|
||||||
|
|
||||||
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();
|
virtual ~Downloader();
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
@@ -185,22 +177,14 @@ class Downloader
|
|||||||
/**
|
/**
|
||||||
* Start a new download.
|
* Start a new download.
|
||||||
*
|
*
|
||||||
* This method is thread safe and returns a pointer to a newly created
|
* This method is thread safe and return a pointer to a newly created `Download`.
|
||||||
* `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.
|
* User should call `update` on the returned `Download` to have an accurate status.
|
||||||
*
|
*
|
||||||
* @param uri: The uri of the thing to download.
|
* @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.
|
* @param options: A series of pair <option_name, option_value> to pass to aria.
|
||||||
* @return: The newly created Download.
|
* @return: The newly created Download.
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Download> startDownload(const std::string& uri, const std::string& downloadDir, Options options = {});
|
std::shared_ptr<Download> startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a download corrsponding to a download id (did)
|
* Get a download corrsponding to a download id (did)
|
||||||
@@ -222,7 +206,7 @@ class Downloader
|
|||||||
*/
|
*/
|
||||||
std::vector<std::string> getDownloadIds() const;
|
std::vector<std::string> getDownloadIds() const;
|
||||||
|
|
||||||
private: // data
|
private:
|
||||||
mutable std::mutex m_lock;
|
mutable std::mutex m_lock;
|
||||||
std::map<std::string, std::shared_ptr<Download>> m_knownDownloads;
|
std::map<std::string, std::shared_ptr<Download>> m_knownDownloads;
|
||||||
std::shared_ptr<Aria2> mp_aria;
|
std::shared_ptr<Aria2> mp_aria;
|
||||||
|
|||||||
File renamed without changes.
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
* 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_I18N
|
|
||||||
#define KIWIX_I18N
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace kiwix
|
|
||||||
{
|
|
||||||
|
|
||||||
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
|
||||||
|
|
||||||
namespace i18n
|
|
||||||
{
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> Parameters;
|
|
||||||
|
|
||||||
std::string expandParameterizedString(const std::string& lang,
|
|
||||||
const std::string& key,
|
|
||||||
const Parameters& params);
|
|
||||||
|
|
||||||
class GetTranslatedString
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit GetTranslatedString(const std::string& lang) : m_lang(lang) {}
|
|
||||||
|
|
||||||
std::string operator()(const std::string& key) const
|
|
||||||
{
|
|
||||||
return getTranslatedString(m_lang, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string operator()(const std::string& key, const Parameters& params) const
|
|
||||||
{
|
|
||||||
return expandParameterizedString(m_lang, key, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const std::string m_lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace i18n
|
|
||||||
|
|
||||||
class ParameterizedMessage
|
|
||||||
{
|
|
||||||
public: // types
|
|
||||||
typedef i18n::Parameters Parameters;
|
|
||||||
|
|
||||||
public: // functions
|
|
||||||
ParameterizedMessage(const std::string& msgId, const Parameters& params)
|
|
||||||
: msgId(msgId)
|
|
||||||
, params(params)
|
|
||||||
{}
|
|
||||||
|
|
||||||
std::string getText(const std::string& lang) const;
|
|
||||||
|
|
||||||
const std::string& getMsgId() const { return msgId; }
|
|
||||||
const Parameters& getParams() const { return params; }
|
|
||||||
|
|
||||||
private: // data
|
|
||||||
const std::string msgId;
|
|
||||||
const Parameters params;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
|
|
||||||
{
|
|
||||||
const ParameterizedMessage::Parameters noParams;
|
|
||||||
return ParameterizedMessage(msgId, noParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string translateBookCategory(const std::string& lang, const std::string& category);
|
|
||||||
|
|
||||||
} // namespace kiwix
|
|
||||||
|
|
||||||
#endif // KIWIX_I18N
|
|
||||||
+10
-141
@@ -34,10 +34,6 @@
|
|||||||
|
|
||||||
#define KIWIX_LIBRARY_VERSION "20110515"
|
#define KIWIX_LIBRARY_VERSION "20110515"
|
||||||
|
|
||||||
namespace Xapian {
|
|
||||||
class WritableDatabase;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -55,22 +51,6 @@ enum supportedListMode {
|
|||||||
NOVALID = 1 << 5
|
NOVALID = 1 << 5
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MigrationMode {
|
|
||||||
/** When migrating bookmarks, do not allow to migrate to an older book than the currently pointed one
|
|
||||||
* (or date stored in the bookmark if book is invalid)
|
|
||||||
*
|
|
||||||
* If no newer books are found, no upgrade is made.
|
|
||||||
*/
|
|
||||||
UPGRADE_ONLY = 0,
|
|
||||||
|
|
||||||
/** Try hard to do a migration. This mostly does:
|
|
||||||
* - Try to find a newer book.
|
|
||||||
* - If book is invalid: find a best book, potentially older.
|
|
||||||
* Older book will never be returned if current book is a valid one.
|
|
||||||
*/
|
|
||||||
ALLOW_DOWNGRADE = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Filter {
|
class Filter {
|
||||||
public: // types
|
public: // types
|
||||||
using Tags = std::vector<std::string>;
|
using Tags = std::vector<std::string>;
|
||||||
@@ -87,7 +67,6 @@ class Filter {
|
|||||||
std::string _query;
|
std::string _query;
|
||||||
bool _queryIsPartial;
|
bool _queryIsPartial;
|
||||||
std::string _name;
|
std::string _name;
|
||||||
std::string _flavour;
|
|
||||||
|
|
||||||
public: // functions
|
public: // functions
|
||||||
Filter();
|
Filter();
|
||||||
@@ -126,12 +105,6 @@ class Filter {
|
|||||||
Filter& acceptTags(const Tags& tags);
|
Filter& acceptTags(const Tags& tags);
|
||||||
Filter& rejectTags(const Tags& tags);
|
Filter& rejectTags(const Tags& tags);
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the filter to only accept books in the specified category.
|
|
||||||
*
|
|
||||||
* Multiple categories can be specified as a comma-separated list (in
|
|
||||||
* which case a book in any of those categories will match).
|
|
||||||
*/
|
|
||||||
Filter& category(std::string category);
|
Filter& category(std::string category);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,7 +120,6 @@ class Filter {
|
|||||||
Filter& maxSize(size_t size);
|
Filter& maxSize(size_t size);
|
||||||
Filter& query(std::string query, bool partial=true);
|
Filter& query(std::string query, bool partial=true);
|
||||||
Filter& name(std::string name);
|
Filter& name(std::string name);
|
||||||
Filter& flavour(std::string flavour);
|
|
||||||
Filter& clearLang();
|
Filter& clearLang();
|
||||||
Filter& clearCategory();
|
Filter& clearCategory();
|
||||||
|
|
||||||
@@ -170,9 +142,6 @@ class Filter {
|
|||||||
bool hasCreator() const;
|
bool hasCreator() const;
|
||||||
const std::string& getCreator() const { return _creator; }
|
const std::string& getCreator() const { return _creator; }
|
||||||
|
|
||||||
bool hasFlavour() const;
|
|
||||||
const std::string& getFlavour() const { return _flavour; }
|
|
||||||
|
|
||||||
const Tags& getAcceptTags() const { return _acceptTags; }
|
const Tags& getAcceptTags() const { return _acceptTags; }
|
||||||
const Tags& getRejectTags() const { return _rejectTags; }
|
const Tags& getRejectTags() const { return _rejectTags; }
|
||||||
|
|
||||||
@@ -198,53 +167,31 @@ class ZimSearcher : public zim::Searcher
|
|||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
class ConcurrentCache;
|
|
||||||
|
|
||||||
template<typename, typename>
|
|
||||||
class MultiKeyCache;
|
|
||||||
|
|
||||||
using LibraryPtr = std::shared_ptr<Library>;
|
|
||||||
using ConstLibraryPtr = std::shared_ptr<const Library>;
|
|
||||||
|
|
||||||
|
|
||||||
// Some compiler we use don't have [[nodiscard]] attribute.
|
|
||||||
// We don't want to declare `create` with it in this case.
|
|
||||||
#define LIBKIWIX_NODISCARD
|
|
||||||
#if defined __has_cpp_attribute
|
|
||||||
#if __has_cpp_attribute (nodiscard)
|
|
||||||
#undef LIBKIWIX_NODISCARD
|
|
||||||
#define LIBKIWIX_NODISCARD [[nodiscard]]
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Library store several books.
|
* A Library store several books.
|
||||||
*/
|
*/
|
||||||
class Library: public std::enable_shared_from_this<Library>
|
class Library
|
||||||
{
|
{
|
||||||
|
// all data fields must be added in LibraryBase
|
||||||
|
mutable std::mutex m_mutex;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef uint64_t Revision;
|
typedef uint64_t Revision;
|
||||||
typedef std::vector<std::string> BookIdCollection;
|
typedef std::vector<std::string> BookIdCollection;
|
||||||
typedef std::map<std::string, int> AttributeCounts;
|
typedef std::map<std::string, int> AttributeCounts;
|
||||||
typedef std::set<std::string> BookIdSet;
|
typedef std::set<std::string> BookIdSet;
|
||||||
|
|
||||||
private:
|
|
||||||
Library();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LIBKIWIX_NODISCARD static LibraryPtr create() {
|
Library();
|
||||||
return LibraryPtr(new Library());
|
|
||||||
}
|
|
||||||
~Library();
|
~Library();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Library is not a copiable object. However it can be moved.
|
* Library is not a copiable object. However it can be moved.
|
||||||
*/
|
*/
|
||||||
Library(const Library& ) = delete;
|
Library(const Library& ) = delete;
|
||||||
Library(Library&& ) = delete;
|
Library(Library&& );
|
||||||
void operator=(const Library& ) = delete;
|
void operator=(const Library& ) = delete;
|
||||||
Library& operator=(Library&& ) = delete;
|
Library& operator=(Library&& );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a book to the library.
|
* Add a book to the library.
|
||||||
@@ -271,7 +218,7 @@ class Library: public std::enable_shared_from_this<Library>
|
|||||||
void addBookmark(const Bookmark& bookmark);
|
void addBookmark(const Bookmark& bookmark);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a bookmark
|
* Remove a bookmarkk
|
||||||
*
|
*
|
||||||
* @param zimId The zimId of the bookmark.
|
* @param zimId The zimId of the bookmark.
|
||||||
* @param url The url of the bookmark.
|
* @param url The url of the bookmark.
|
||||||
@@ -279,66 +226,6 @@ class Library: public std::enable_shared_from_this<Library>
|
|||||||
*/
|
*/
|
||||||
bool removeBookmark(const std::string& zimId, const std::string& url);
|
bool removeBookmark(const std::string& zimId, const std::string& url);
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all invalid bookmarks.
|
|
||||||
*
|
|
||||||
* All invalid bookmarks (ie pointing to unknown books, no check is made on bookmark pointing to
|
|
||||||
* invalid articles of valid book) will be migrated (if possible) to a better book.
|
|
||||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
|
||||||
*
|
|
||||||
* @return A tuple<int, int>: <The number of bookmarks updated>, <Number of invalid bookmarks before migration was performed>.
|
|
||||||
*/
|
|
||||||
std::tuple<int, int> migrateBookmarks(MigrationMode migrationMode = ALLOW_DOWNGRADE);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate all bookmarks associated to a specific book.
|
|
||||||
*
|
|
||||||
* All bookmarks associated to `sourceBookId` book will be migrated to a better book.
|
|
||||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
|
||||||
*
|
|
||||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
|
||||||
* @param migrationMode how we will find the best book.
|
|
||||||
* @return The number of bookmarks updated.
|
|
||||||
*/
|
|
||||||
int migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode = UPGRADE_ONLY);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate bookmarks
|
|
||||||
*
|
|
||||||
* Migrate all bookmarks pointing to `source` to `destination`.
|
|
||||||
*
|
|
||||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
|
||||||
* @param targetBookId the destination bookId to migrate the bookmarks to.
|
|
||||||
* @return The number of bookmarks updated.
|
|
||||||
*/
|
|
||||||
int migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the best available bookId for a bookmark.
|
|
||||||
*
|
|
||||||
* Given a bookmark, return the best available bookId.
|
|
||||||
* "best available bookId" is determined using heuristitcs based on book name, flavour and date.
|
|
||||||
*
|
|
||||||
* @param bookmark The bookmark to search the bookId for.
|
|
||||||
* @param migrationMode The migration mode to use.
|
|
||||||
* @return A bookId. Potentially empty string if no suitable book found.
|
|
||||||
*/
|
|
||||||
std::string getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the best bookId for a combination of book's name, flavour and date.
|
|
||||||
*
|
|
||||||
* Given a bookName (mandatory), try to find the best book.
|
|
||||||
* If preferedFlavour is given, will try to find a book with the same flavour. If not found, return a book with a different flavour.
|
|
||||||
* If minDate is given, return a book newer than minDate. If not found, return a empty bookId.
|
|
||||||
*
|
|
||||||
* @param bookName The name of the book
|
|
||||||
* @param preferedFlavour The prefered flavour.
|
|
||||||
* @param minDate the minimal book date acceptable. Must be a string in the format "YYYY-MM-DD".
|
|
||||||
* @return A bookId corresponding to the query, or empty string if not found.
|
|
||||||
*/
|
|
||||||
std::string getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour="", const std::string& minDate="") const;
|
|
||||||
|
|
||||||
// XXX: This is a non-thread-safe operation
|
// XXX: This is a non-thread-safe operation
|
||||||
const Book& getBookById(const std::string& id) const;
|
const Book& getBookById(const std::string& id) const;
|
||||||
// XXX: This is a non-thread-safe operation
|
// XXX: This is a non-thread-safe operation
|
||||||
@@ -475,37 +362,19 @@ class Library: public std::enable_shared_from_this<Library>
|
|||||||
|
|
||||||
private: // types
|
private: // types
|
||||||
typedef const std::string& (Book::*BookStrPropMemFn)() const;
|
typedef const std::string& (Book::*BookStrPropMemFn)() const;
|
||||||
struct Entry : Book
|
struct Impl;
|
||||||
{
|
|
||||||
Library::Revision lastUpdatedRevision = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
private: // functions
|
private: // functions
|
||||||
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
|
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
|
||||||
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
||||||
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
||||||
std::string getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const;
|
|
||||||
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
|
|
||||||
void updateBookDB(const Book& book);
|
void updateBookDB(const Book& book);
|
||||||
void dropCache(const std::string& bookId);
|
void dropCache(const std::string& bookId);
|
||||||
|
|
||||||
private: //data
|
private: //data
|
||||||
mutable std::recursive_mutex m_mutex;
|
std::unique_ptr<Impl> mp_impl;
|
||||||
Library::Revision m_revision;
|
|
||||||
std::map<std::string, Entry> m_books;
|
|
||||||
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
|
|
||||||
std::unique_ptr<ArchiveCache> mp_archiveCache;
|
|
||||||
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
|
|
||||||
std::unique_ptr<SearcherCache> mp_searcherCache;
|
|
||||||
std::vector<kiwix::Bookmark> m_bookmarks;
|
|
||||||
std::unique_ptr<Xapian::WritableDatabase> m_bookDB;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// We don't need it anymore and we don't want to polute any other potential usage
|
|
||||||
// of `LIBKIWIX_NODISCARD` token.
|
|
||||||
#undef LIBKIWIX_NODISCARD
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
File renamed without changes.
File renamed without changes.
+6
-6
@@ -37,10 +37,10 @@ namespace kiwix
|
|||||||
class LibraryManipulator
|
class LibraryManipulator
|
||||||
{
|
{
|
||||||
public: // functions
|
public: // functions
|
||||||
explicit LibraryManipulator(LibraryPtr library);
|
explicit LibraryManipulator(Library* library);
|
||||||
virtual ~LibraryManipulator();
|
virtual ~LibraryManipulator();
|
||||||
|
|
||||||
LibraryPtr getLibrary() const { return library; }
|
Library& getLibrary() const { return library; }
|
||||||
|
|
||||||
bool addBookToLibrary(const Book& book);
|
bool addBookToLibrary(const Book& book);
|
||||||
void addBookmarkToLibrary(const Bookmark& bookmark);
|
void addBookmarkToLibrary(const Bookmark& bookmark);
|
||||||
@@ -52,7 +52,7 @@ class LibraryManipulator
|
|||||||
virtual void booksWereRemovedFromLibrary();
|
virtual void booksWereRemovedFromLibrary();
|
||||||
|
|
||||||
private: // data
|
private: // data
|
||||||
LibraryPtr library;
|
kiwix::Library& library;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,8 +64,8 @@ class Manager
|
|||||||
typedef std::vector<std::string> Paths;
|
typedef std::vector<std::string> Paths;
|
||||||
|
|
||||||
public: // functions
|
public: // functions
|
||||||
explicit Manager(LibraryManipulator manipulator);
|
explicit Manager(LibraryManipulator* manipulator);
|
||||||
explicit Manager(LibraryPtr library);
|
explicit Manager(Library* library);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a `library.xml` and add book in the file to the library.
|
* Read a `library.xml` and add book in the file to the library.
|
||||||
@@ -163,7 +163,7 @@ class Manager
|
|||||||
uint64_t m_itemsPerPage = 0;
|
uint64_t m_itemsPerPage = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
kiwix::LibraryManipulator manipulator;
|
std::shared_ptr<kiwix::LibraryManipulator> manipulator;
|
||||||
|
|
||||||
bool readBookFromPath(const std::string& path, Book* book);
|
bool readBookFromPath(const std::string& path, Book* book);
|
||||||
bool parseXmlDom(const pugi::xml_document& doc,
|
bool parseXmlDom(const pugi::xml_document& doc,
|
||||||
|
|||||||
+3
-2
@@ -4,14 +4,15 @@ headers = [
|
|||||||
'common.h',
|
'common.h',
|
||||||
'library.h',
|
'library.h',
|
||||||
'manager.h',
|
'manager.h',
|
||||||
|
'libxml_dumper.h',
|
||||||
|
'opds_dumper.h',
|
||||||
'downloader.h',
|
'downloader.h',
|
||||||
'search_renderer.h',
|
'search_renderer.h',
|
||||||
'server.h',
|
'server.h',
|
||||||
'kiwixserve.h',
|
'kiwixserve.h',
|
||||||
'name_mapper.h',
|
'name_mapper.h',
|
||||||
'tools.h',
|
'tools.h',
|
||||||
'version.h',
|
'version.h'
|
||||||
'i18n.h'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
install_headers(headers, subdir:'kiwix')
|
install_headers(headers, subdir:'kiwix')
|
||||||
@@ -54,15 +54,12 @@ class HumanReadableNameMapper : public NameMapper {
|
|||||||
virtual ~HumanReadableNameMapper() = default;
|
virtual ~HumanReadableNameMapper() = default;
|
||||||
virtual std::string getNameForId(const std::string& id) const;
|
virtual std::string getNameForId(const std::string& id) const;
|
||||||
virtual std::string getIdForName(const std::string& name) 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 {
|
class UpdatableNameMapper : public NameMapper {
|
||||||
typedef std::shared_ptr<NameMapper> NameMapperHandle;
|
typedef std::shared_ptr<NameMapper> NameMapperHandle;
|
||||||
public:
|
public:
|
||||||
UpdatableNameMapper(std::shared_ptr<Library> library, bool withAlias);
|
UpdatableNameMapper(Library& library, bool withAlias);
|
||||||
|
|
||||||
virtual std::string getNameForId(const std::string& id) const;
|
virtual std::string getNameForId(const std::string& id) const;
|
||||||
virtual std::string getIdForName(const std::string& name) const;
|
virtual std::string getIdForName(const std::string& name) const;
|
||||||
@@ -74,7 +71,7 @@ class UpdatableNameMapper : public NameMapper {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
mutable std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
std::shared_ptr<Library> library;
|
Library& library;
|
||||||
NameMapperHandle nameMapper;
|
NameMapperHandle nameMapper;
|
||||||
const bool withAlias;
|
const bool withAlias;
|
||||||
};
|
};
|
||||||
|
|||||||
File renamed without changes.
+25
-23
@@ -37,11 +37,29 @@ class SearchRenderer
|
|||||||
/**
|
/**
|
||||||
* Construct a SearchRenderer from a SearchResultSet.
|
* Construct a SearchRenderer from a SearchResultSet.
|
||||||
*
|
*
|
||||||
|
* The constructed version of the SearchRenderer will not introduce
|
||||||
|
* the book name for each result. It is better to use the other constructor
|
||||||
|
* with a Library pointer to have a better html page.
|
||||||
|
*
|
||||||
* @param srs The `SearchResultSet` to render.
|
* @param srs The `SearchResultSet` to render.
|
||||||
|
* @param mapper The `NameMapper` to use to do the rendering.
|
||||||
* @param start The start offset used for the srs.
|
* @param start The start offset used for the srs.
|
||||||
* @param estimatedResultCount The estimatedResultCount of the whole search
|
* @param estimatedResultCount The estimatedResultCount of the whole search
|
||||||
*/
|
*/
|
||||||
SearchRenderer(zim::SearchResultSet srs, unsigned int start, unsigned int estimatedResultCount);
|
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
|
||||||
|
unsigned int start, unsigned int estimatedResultCount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a SearchRenderer from a SearchResultSet.
|
||||||
|
*
|
||||||
|
* @param srs The `SearchResultSet` to render.
|
||||||
|
* @param mapper The `NameMapper` to use to do the rendering.
|
||||||
|
* @param library The `Library` to use to look up book details for search results.
|
||||||
|
* @param start The start offset used for the srs.
|
||||||
|
* @param estimatedResultCount The estimatedResultCount of the whole search
|
||||||
|
*/
|
||||||
|
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
|
||||||
|
unsigned int start, unsigned int estimatedResultCount);
|
||||||
|
|
||||||
~SearchRenderer();
|
~SearchRenderer();
|
||||||
|
|
||||||
@@ -72,39 +90,24 @@ class SearchRenderer
|
|||||||
this->pageLength = pageLength;
|
this->pageLength = pageLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
std::string renderTemplate(const std::string& tmpl_str);
|
||||||
* set user language
|
|
||||||
*/
|
|
||||||
void setUserLang(const std::string& lang){
|
|
||||||
this->userlang = lang;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the html page with the resutls of the search.
|
* Generate the html page with the resutls of the search.
|
||||||
*
|
|
||||||
* @param mapper The `NameMapper` to use to do the rendering.
|
|
||||||
* @param library The `Library` to use to look up book details for search results.
|
|
||||||
May be nullptr. In this case, bookName is not set in the rendered string.
|
|
||||||
* @return The html string
|
|
||||||
*/
|
*/
|
||||||
std::string getHtml(const NameMapper& mapper, const Library* library);
|
std::string getHtml();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the xml page with the resutls of the search.
|
* Generate the xml page with the resutls of the search.
|
||||||
*
|
|
||||||
* @param mapper The `NameMapper` to use to do the rendering.
|
|
||||||
* @param library The `Library` to use to look up book details for search results.
|
|
||||||
May be nullptr. In this case, bookName is not set in the rendered string.
|
|
||||||
* @return The xml string
|
|
||||||
*/
|
*/
|
||||||
std::string getXml(const NameMapper& mapper, const Library* library);
|
std::string getXml();
|
||||||
|
|
||||||
protected: // function
|
|
||||||
std::string renderTemplate(const std::string& tmpl_str, const NameMapper& mapper, const Library *library);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string beautifyInteger(const unsigned int number);
|
std::string beautifyInteger(const unsigned int number);
|
||||||
zim::SearchResultSet m_srs;
|
zim::SearchResultSet m_srs;
|
||||||
|
NameMapper* mp_nameMapper;
|
||||||
|
Library* mp_library;
|
||||||
std::string searchBookQuery;
|
std::string searchBookQuery;
|
||||||
std::string searchPattern;
|
std::string searchPattern;
|
||||||
std::string protocolPrefix;
|
std::string protocolPrefix;
|
||||||
@@ -112,7 +115,6 @@ class SearchRenderer
|
|||||||
unsigned int pageLength;
|
unsigned int pageLength;
|
||||||
unsigned int estimatedResultCount;
|
unsigned int estimatedResultCount;
|
||||||
unsigned int resultStart;
|
unsigned int resultStart;
|
||||||
std::string userlang = "en";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
-11
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "tools.h"
|
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
@@ -37,7 +36,7 @@ namespace kiwix
|
|||||||
*
|
*
|
||||||
* @param library The library to serve.
|
* @param library The library to serve.
|
||||||
*/
|
*/
|
||||||
Server(std::shared_ptr<Library> library, std::shared_ptr<NameMapper> nameMapper=nullptr);
|
Server(Library* library, NameMapper* nameMapper=nullptr);
|
||||||
|
|
||||||
virtual ~Server();
|
virtual ~Server();
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ namespace kiwix
|
|||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void setRoot(const std::string& root);
|
void setRoot(const std::string& root);
|
||||||
void setAddress(const std::string& addr);
|
void setAddress(const std::string& addr) { m_addr = addr; }
|
||||||
void setPort(int port) { m_port = port; }
|
void setPort(int port) { m_port = port; }
|
||||||
void setNbThreads(int threads) { m_nbThreads = threads; }
|
void setNbThreads(int threads) { m_nbThreads = threads; }
|
||||||
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
|
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
|
||||||
@@ -63,16 +62,14 @@ namespace kiwix
|
|||||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||||
void setBlockExternalLinks(bool blockExternalLinks)
|
void setBlockExternalLinks(bool blockExternalLinks)
|
||||||
{ m_blockExternalLinks = blockExternalLinks; }
|
{ m_blockExternalLinks = blockExternalLinks; }
|
||||||
void setIpMode(IpMode mode) { m_ipMode = mode; }
|
int getPort();
|
||||||
int getPort() const;
|
std::string getAddress();
|
||||||
IpAddress getAddress() const;
|
|
||||||
IpMode getIpMode() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<Library> mp_library;
|
Library* mp_library;
|
||||||
std::shared_ptr<NameMapper> mp_nameMapper;
|
NameMapper* mp_nameMapper;
|
||||||
std::string m_root = "";
|
std::string m_root = "";
|
||||||
IpAddress m_addr;
|
std::string m_addr = "";
|
||||||
std::string m_indexTemplateString = "";
|
std::string m_indexTemplateString = "";
|
||||||
int m_port = 80;
|
int m_port = 80;
|
||||||
int m_nbThreads = 1;
|
int m_nbThreads = 1;
|
||||||
@@ -81,7 +78,6 @@ namespace kiwix
|
|||||||
bool m_withTaskbar = true;
|
bool m_withTaskbar = true;
|
||||||
bool m_withLibraryButton = true;
|
bool m_withLibraryButton = true;
|
||||||
bool m_blockExternalLinks = false;
|
bool m_blockExternalLinks = false;
|
||||||
IpMode m_ipMode = IpMode::AUTO;
|
|
||||||
int m_ipConnectionLimit = 0;
|
int m_ipConnectionLimit = 0;
|
||||||
std::unique_ptr<InternalServer> mp_server;
|
std::unique_ptr<InternalServer> mp_server;
|
||||||
};
|
};
|
||||||
|
|||||||
+22
-73
@@ -23,21 +23,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <cstdint>
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix {
|
||||||
{
|
|
||||||
|
|
||||||
struct IpAddress
|
|
||||||
{
|
|
||||||
std::string addr; // IPv4 address
|
|
||||||
std::string addr6; // IPv6 address
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::pair<std::string, std::string> LangNameCodePair;
|
|
||||||
typedef std::vector<LangNameCodePair> FeedLanguages;
|
|
||||||
typedef std::vector<std::string> FeedCategories;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current directory.
|
* Return the current directory.
|
||||||
@@ -46,6 +33,26 @@ typedef std::vector<std::string> FeedCategories;
|
|||||||
*/
|
*/
|
||||||
std::string getCurrentDirectory();
|
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
|
/** Return the path of the executable
|
||||||
*
|
*
|
||||||
* Some application may be packaged in auto extractible archive (Appimage) and the
|
* Some application may be packaged in auto extractible archive (Appimage) and the
|
||||||
@@ -199,73 +206,15 @@ bool fileReadable(const std::string& path);
|
|||||||
std::string getMimeTypeForFile(const std::string& filename);
|
std::string getMimeTypeForFile(const std::string& filename);
|
||||||
|
|
||||||
/** Provides all available network interfaces
|
/** 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
|
* 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();
|
std::map<std::string, std::string> getNetworkInterfaces();
|
||||||
|
|
||||||
/** Provides the best IP address
|
/** Provides the best IP address
|
||||||
* This function provides the best IP addresses for both ipv4 and ipv6 protocols,
|
* This function provides the best IP address from the list given by getNetworkInterfaces
|
||||||
* 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();
|
std::string getBestPublicIp();
|
||||||
|
|
||||||
/** Converts file size to human readable format.
|
|
||||||
*
|
|
||||||
* This function will convert a number to its equivalent size using units.
|
|
||||||
*
|
|
||||||
* @param number file size in bytes.
|
|
||||||
* @return a human-readable string representation of the size, e.g., "2.3 KB", "1.8 MB", "5.2 GB".
|
|
||||||
*/
|
|
||||||
std::string beautifyFileSize(uint64_t number);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load languages stored in an OPDS stream.
|
|
||||||
*
|
|
||||||
* @param content the OPDS stream.
|
|
||||||
* @return vector containing pairs of language code and their corresponding full language name.
|
|
||||||
*/
|
|
||||||
FeedLanguages readLanguagesFromFeed(const std::string& content);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load categories stored in an OPDS stream .
|
|
||||||
*
|
|
||||||
* @param content the OPDS stream.
|
|
||||||
* @return vector containing category strings.
|
|
||||||
*/
|
|
||||||
FeedCategories readCategoriesFromFeed(const std::string& content);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the full language name associated with a given ISO 639-3 language code.
|
|
||||||
*
|
|
||||||
* @param lang ISO 639-3 language code.
|
|
||||||
* @return full language name.
|
|
||||||
*/
|
|
||||||
std::string getLanguageSelfName(const std::string& lang);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
#endif // KIWIX_TOOLS_H
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
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@
|
||||||
+25
-19
@@ -1,7 +1,7 @@
|
|||||||
project('libkiwix', 'cpp',
|
project('libkiwix', 'cpp',
|
||||||
version : '14.0.0',
|
version : '12.0.0',
|
||||||
license : 'GPLv3+',
|
license : 'GPLv3+',
|
||||||
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
|
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
|
||||||
|
|
||||||
compiler = meson.get_compiler('cpp')
|
compiler = meson.get_compiler('cpp')
|
||||||
|
|
||||||
@@ -35,10 +35,9 @@ else
|
|||||||
error('Cannot found header mustache.hpp')
|
error('Cannot found header mustache.hpp')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libzim_dep = dependency('libzim', version:['>=9.0.0', '<10.0.0'], static:static_deps)
|
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
|
||||||
|
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN')
|
||||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
|
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
|
||||||
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
@@ -50,14 +49,8 @@ endif
|
|||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
||||||
extra_libs += ['-liphlpapi']
|
|
||||||
endif
|
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]
|
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
|
||||||
|
|
||||||
inc = include_directories('include', extra_include)
|
inc = include_directories('include', extra_include)
|
||||||
@@ -65,6 +58,12 @@ inc = include_directories('include', extra_include)
|
|||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
conf.set('LIBKIWIX_VERSION', '"@0@"'.format(meson.project_version()))
|
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('include')
|
||||||
subdir('scripts')
|
subdir('scripts')
|
||||||
subdir('static')
|
subdir('static')
|
||||||
@@ -74,10 +73,17 @@ if get_option('doc')
|
|||||||
subdir('docs')
|
subdir('docs')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
pkg_mod = import('pkgconfig')
|
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl', 'libmicrohttpd', 'xapian-core']
|
||||||
pkg_mod.generate(libraries : [libkiwix] + extra_libs,
|
|
||||||
version : meson.project_version(),
|
pkg_conf = configuration_data()
|
||||||
name : 'libkiwix',
|
pkg_conf.set('prefix', get_option('prefix'))
|
||||||
filebase : 'libkiwix',
|
pkg_conf.set('requires', ' '.join(pkg_requires))
|
||||||
description : 'A library that contains useful primitives that Kiwix readers have in common',
|
pkg_conf.set('extra_libs', ' '.join(extra_libs))
|
||||||
extra_cflags: extra_cflags)
|
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'
|
||||||
|
)
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ lang_table_entry_cxx_template = '''
|
|||||||
|
|
||||||
cxxfile_template = '''// This file is automatically generated. Do not modify it.
|
cxxfile_template = '''// This file is automatically generated. Do not modify it.
|
||||||
|
|
||||||
#include "server/i18n_utils.h"
|
#include "server/i18n.h"
|
||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
namespace i18n {
|
namespace i18n {
|
||||||
|
|||||||
@@ -61,32 +61,6 @@ resource_decl_template = """{namespaces_open}
|
|||||||
extern const std::string {identifier};
|
extern const std::string {identifier};
|
||||||
{namespaces_close}"""
|
{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:
|
class Resource:
|
||||||
def __init__(self, base_dirs, filename, cacheid=None):
|
def __init__(self, base_dirs, filename, cacheid=None):
|
||||||
filename = filename
|
filename = filename
|
||||||
@@ -98,8 +72,6 @@ class Resource:
|
|||||||
try:
|
try:
|
||||||
with open(os.path.join(base_dir, filename), 'rb') as f:
|
with open(os.path.join(base_dir, filename), 'rb') as f:
|
||||||
self.data = f.read()
|
self.data = f.read()
|
||||||
if not is_binary_resource(filename):
|
|
||||||
self.data = self.data.replace(b"\r\n", b"\n")
|
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
+12
-45
@@ -4,7 +4,6 @@
|
|||||||
#include "xmlrpc.h"
|
#include "xmlrpc.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -30,41 +29,18 @@
|
|||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
|
|
||||||
namespace {
|
Aria2::Aria2():
|
||||||
|
|
||||||
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),
|
mp_aria(nullptr),
|
||||||
m_port(42042),
|
m_port(42042),
|
||||||
m_secret(getNewRpcSecret())
|
m_secret(getNewRpcSecret())
|
||||||
{
|
{
|
||||||
|
m_downloadDir = getDataDirectory();
|
||||||
|
makeDirectory(m_downloadDir);
|
||||||
std::vector<const char*> callCmd;
|
std::vector<const char*> callCmd;
|
||||||
|
|
||||||
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
|
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
|
||||||
std::string session_file = appendToDirectory(sessionFileDir, "kiwix.session");
|
std::string download_dir = "--dir=" + getDataDirectory();
|
||||||
pauseAnyActiveDownloads(session_file);
|
std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session");
|
||||||
std::string session = "--save-session=" + session_file;
|
std::string session = "--save-session=" + session_file;
|
||||||
std::string inputFile = "--input-file=" + session_file;
|
std::string inputFile = "--input-file=" + session_file;
|
||||||
// std::string log_dir = "--log=\"" + logDir + "\"";
|
// std::string log_dir = "--log=\"" + logDir + "\"";
|
||||||
@@ -91,6 +67,7 @@ Aria2::Aria2(std::string sessionFileDir):
|
|||||||
callCmd.push_back("--enable-rpc");
|
callCmd.push_back("--enable-rpc");
|
||||||
callCmd.push_back(rpc_secret.c_str());
|
callCmd.push_back(rpc_secret.c_str());
|
||||||
callCmd.push_back(rpc_port.c_str());
|
callCmd.push_back(rpc_port.c_str());
|
||||||
|
callCmd.push_back(download_dir.c_str());
|
||||||
if (fileReadable(session_file)) {
|
if (fileReadable(session_file)) {
|
||||||
callCmd.push_back(inputFile.c_str());
|
callCmd.push_back(inputFile.c_str());
|
||||||
}
|
}
|
||||||
@@ -120,30 +97,20 @@ Aria2::Aria2(std::string sessionFileDir):
|
|||||||
curl_easy_setopt(p_curl, CURLOPT_PORT, m_port);
|
curl_easy_setopt(p_curl, CURLOPT_PORT, m_port);
|
||||||
curl_easy_setopt(p_curl, CURLOPT_POST, 1L);
|
curl_easy_setopt(p_curl, CURLOPT_POST, 1L);
|
||||||
curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
|
curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
|
||||||
curl_easy_setopt(p_curl, CURLOPT_TIMEOUT_MS, 100);
|
|
||||||
|
|
||||||
typedef std::chrono::duration<double> Seconds;
|
int watchdog = 50;
|
||||||
|
while(--watchdog) {
|
||||||
const double MAX_WAITING_TIME_SECONDS = 0.5;
|
|
||||||
const auto t0 = std::chrono::steady_clock::now();
|
|
||||||
bool maxWaitingTimeWasExceeded = false;
|
|
||||||
|
|
||||||
CURLcode res = CURLE_OK;
|
|
||||||
while ( !maxWaitingTimeWasExceeded ) {
|
|
||||||
sleep(10);
|
sleep(10);
|
||||||
curlErrorBuffer[0] = 0;
|
curlErrorBuffer[0] = 0;
|
||||||
res = curl_easy_perform(p_curl);
|
auto res = curl_easy_perform(p_curl);
|
||||||
if (res == CURLE_OK) {
|
if (res == CURLE_OK) {
|
||||||
break;
|
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);
|
curl_easy_cleanup(p_curl);
|
||||||
if ( maxWaitingTimeWasExceeded ) {
|
if (!watchdog) {
|
||||||
LOG_ARIA_ERROR();
|
|
||||||
throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd);
|
throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -22,10 +22,11 @@ class Aria2
|
|||||||
std::unique_ptr<Subprocess> mp_aria;
|
std::unique_ptr<Subprocess> mp_aria;
|
||||||
int m_port;
|
int m_port;
|
||||||
std::string m_secret;
|
std::string m_secret;
|
||||||
|
std::string m_downloadDir;
|
||||||
std::string doRequest(const MethodCall& methodCall);
|
std::string doRequest(const MethodCall& methodCall);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Aria2(std::string sessionFileDir);
|
Aria2();
|
||||||
virtual ~Aria2() = default;
|
virtual ~Aria2() = default;
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -117,12 +117,11 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
|||||||
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
|
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
|
||||||
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
|
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
|
||||||
m_size = strtoull(ATTR("size"), 0, 0) << 10;
|
m_size = strtoull(ATTR("size"), 0, 0) << 10;
|
||||||
const std::string faviconMimeType = ATTR("faviconMimeType");
|
std::string favicon_mimetype = ATTR("faviconMimeType");
|
||||||
const std::string faviconBase64EncodedData = ATTR("favicon");
|
if (! favicon_mimetype.empty()) {
|
||||||
if ( !faviconMimeType.empty() && !faviconBase64EncodedData.empty() ) {
|
|
||||||
const auto favicon = std::make_shared<Illustration>();
|
const auto favicon = std::make_shared<Illustration>();
|
||||||
favicon->data = base64_decode(faviconBase64EncodedData);
|
favicon->data = base64_decode(ATTR("favicon"));
|
||||||
favicon->mimeType = faviconMimeType;
|
favicon->mimeType = favicon_mimetype;
|
||||||
favicon->url = ATTR("faviconUrl");
|
favicon->url = ATTR("faviconUrl");
|
||||||
m_illustrations.assign(1, favicon);
|
m_illustrations.assign(1, favicon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bookmark.h"
|
#include "bookmark.h"
|
||||||
#include "book.h"
|
|
||||||
|
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
@@ -29,17 +28,6 @@ Bookmark::Bookmark()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Bookmark::Bookmark(const Book& book, const std::string& path, const std::string& title):
|
|
||||||
m_bookId(book.getId()),
|
|
||||||
m_bookTitle(book.getTitle()),
|
|
||||||
m_bookName(book.getName()),
|
|
||||||
m_bookFlavour(book.getFlavour()),
|
|
||||||
m_url(path),
|
|
||||||
m_title(title),
|
|
||||||
m_language(book.getCommaSeparatedLanguages()),
|
|
||||||
m_date(book.getDate())
|
|
||||||
{}
|
|
||||||
|
|
||||||
/* Destructor */
|
/* Destructor */
|
||||||
Bookmark::~Bookmark()
|
Bookmark::~Bookmark()
|
||||||
{
|
{
|
||||||
@@ -50,8 +38,6 @@ void Bookmark::updateFromXml(const pugi::xml_node& node)
|
|||||||
auto bookNode = node.child("book");
|
auto bookNode = node.child("book");
|
||||||
m_bookId = bookNode.child("id").child_value();
|
m_bookId = bookNode.child("id").child_value();
|
||||||
m_bookTitle = bookNode.child("title").child_value();
|
m_bookTitle = bookNode.child("title").child_value();
|
||||||
m_bookName = bookNode.child("name").child_value();
|
|
||||||
m_bookFlavour = bookNode.child("flavour").child_value();
|
|
||||||
m_language = bookNode.child("language").child_value();
|
m_language = bookNode.child("language").child_value();
|
||||||
m_date = bookNode.child("date").child_value();
|
m_date = bookNode.child("date").child_value();
|
||||||
m_title = node.child("title").child_value();
|
m_title = node.child("title").child_value();
|
||||||
|
|||||||
+6
-52
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "downloader.h"
|
#include "downloader.h"
|
||||||
#include "tools.h"
|
|
||||||
#include "tools/pathTools.h"
|
#include "tools/pathTools.h"
|
||||||
#include "tools/stringTools.h"
|
#include "tools/stringTools.h"
|
||||||
|
|
||||||
@@ -125,8 +124,8 @@ void Download::cancelDownload()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
Downloader::Downloader(std::string sessionFileDir) :
|
Downloader::Downloader() :
|
||||||
mp_aria(new Aria2(sessionFileDir))
|
mp_aria(new Aria2())
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
for (auto gid : mp_aria->tellWaiting()) {
|
for (auto gid : mp_aria->tellWaiting()) {
|
||||||
@@ -151,20 +150,11 @@ Downloader::Downloader(std::string sessionFileDir) :
|
|||||||
/* Destructor */
|
/* Destructor */
|
||||||
Downloader::~Downloader()
|
Downloader::~Downloader()
|
||||||
{
|
{
|
||||||
close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downloader::close()
|
void Downloader::close()
|
||||||
{
|
{
|
||||||
if ( mp_aria ) {
|
mp_aria->close();
|
||||||
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 {
|
std::vector<std::string> Downloader::getDownloadIds() const {
|
||||||
@@ -176,49 +166,13 @@ std::vector<std::string> Downloader::getDownloadIds() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options)
|
||||||
{
|
|
||||||
|
|
||||||
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);
|
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) {
|
for (auto& p: m_knownDownloads) {
|
||||||
auto& d = p.second;
|
auto& d = p.second;
|
||||||
if ( downloadCanBeReused(*d, uri, options) )
|
auto& uris = d->getUris();
|
||||||
|
if (std::find(uris.begin(), uris.end(), uri) != uris.end())
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
std::vector<std::string> uris = {uri};
|
std::vector<std::string> uris = {uri};
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
#include "tools/otherTools.h"
|
#include "tools/otherTools.h"
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
#include "tools/regexTools.h"
|
#include "tools/regexTools.h"
|
||||||
#include "server/i18n_utils.h"
|
#include "server/i18n.h"
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
@@ -77,7 +77,7 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
|
|||||||
const auto tags = bookObj.getTags();
|
const auto tags = bookObj.getTags();
|
||||||
const auto downloadAvailable = (bookObj.getUrl() != "");
|
const auto downloadAvailable = (bookObj.getUrl() != "");
|
||||||
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
|
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
|
||||||
|
|
||||||
booksData.push_back(kainjow::mustache::object{
|
booksData.push_back(kainjow::mustache::object{
|
||||||
{"id", contentId},
|
{"id", contentId},
|
||||||
{"title", bookTitle},
|
{"title", bookTitle},
|
||||||
|
|||||||
+121
-255
@@ -39,8 +39,6 @@
|
|||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -60,8 +58,6 @@ bool booksReferToTheSameArchive(const Book& book1, const Book& book2)
|
|||||||
&& book1.getPath() == book2.getPath();
|
&& book1.getPath() == book2.getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // unnamed namespace
|
|
||||||
|
|
||||||
template<typename Key, typename Value>
|
template<typename Key, typename Value>
|
||||||
class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
|
class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
|
||||||
{
|
{
|
||||||
@@ -83,8 +79,49 @@ class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
|
struct Library::Impl
|
||||||
|
{
|
||||||
|
struct Entry : Book
|
||||||
|
{
|
||||||
|
Library::Revision lastUpdatedRevision = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Library::Revision m_revision;
|
||||||
|
std::map<std::string, Entry> m_books;
|
||||||
|
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
|
||||||
|
std::unique_ptr<ArchiveCache> mp_archiveCache;
|
||||||
|
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
|
||||||
|
std::unique_ptr<SearcherCache> mp_searcherCache;
|
||||||
|
std::vector<kiwix::Bookmark> m_bookmarks;
|
||||||
|
Xapian::WritableDatabase m_bookDB;
|
||||||
|
|
||||||
|
unsigned int getBookCount(const bool localBooks, const bool remoteBooks) const;
|
||||||
|
|
||||||
|
Impl();
|
||||||
|
~Impl();
|
||||||
|
|
||||||
|
Impl(Impl&& );
|
||||||
|
Impl& operator=(Impl&& );
|
||||||
|
};
|
||||||
|
|
||||||
|
Library::Impl::Impl()
|
||||||
|
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
|
||||||
|
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
|
||||||
|
m_bookDB("", Xapian::DB_BACKEND_INMEMORY)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Library::Impl::~Impl()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Library::Impl::Impl(Library::Impl&& ) = default;
|
||||||
|
Library::Impl& Library::Impl::operator=(Library::Impl&& ) = default;
|
||||||
|
|
||||||
unsigned int
|
unsigned int
|
||||||
Library::getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const
|
Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const
|
||||||
{
|
{
|
||||||
unsigned int result = 0;
|
unsigned int result = 0;
|
||||||
for (auto& pair: m_books) {
|
for (auto& pair: m_books) {
|
||||||
@@ -99,41 +136,50 @@ Library::getBookCount_not_protected(const bool localBooks, const bool remoteBook
|
|||||||
|
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
Library::Library()
|
Library::Library()
|
||||||
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
|
: mp_impl(new Library::Impl)
|
||||||
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
|
|
||||||
m_bookDB(new Xapian::WritableDatabase("", Xapian::DB_BACKEND_INMEMORY))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Library::Library(Library&& other)
|
||||||
|
: mp_impl(std::move(other.mp_impl))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Library& Library::operator=(Library&& other)
|
||||||
|
{
|
||||||
|
mp_impl = std::move(other.mp_impl);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
/* Destructor */
|
/* Destructor */
|
||||||
Library::~Library() = default;
|
Library::~Library() = default;
|
||||||
|
|
||||||
bool Library::addBook(const Book& book)
|
bool Library::addBook(const Book& book)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
++m_revision;
|
++mp_impl->m_revision;
|
||||||
/* Try to find it */
|
/* Try to find it */
|
||||||
updateBookDB(book);
|
updateBookDB(book);
|
||||||
try {
|
try {
|
||||||
auto& oldbook = m_books.at(book.getId());
|
auto& oldbook = mp_impl->m_books.at(book.getId());
|
||||||
if ( ! booksReferToTheSameArchive(oldbook, book) ) {
|
if ( ! booksReferToTheSameArchive(oldbook, book) ) {
|
||||||
dropCache(book.getId());
|
dropCache(book.getId());
|
||||||
}
|
}
|
||||||
oldbook.update(book); // XXX: This may have no effect if oldbook is readonly
|
oldbook.update(book); // XXX: This may have no effect if oldbook is readonly
|
||||||
// XXX: Then m_bookDB will become out-of-sync with
|
// XXX: Then m_bookDB will become out-of-sync with
|
||||||
// XXX: the real contents of the library.
|
// XXX: the real contents of the library.
|
||||||
oldbook.lastUpdatedRevision = m_revision;
|
oldbook.lastUpdatedRevision = mp_impl->m_revision;
|
||||||
return false;
|
return false;
|
||||||
} catch (std::out_of_range&) {
|
} catch (std::out_of_range&) {
|
||||||
auto& newEntry = m_books[book.getId()];
|
auto& newEntry = mp_impl->m_books[book.getId()];
|
||||||
static_cast<Book&>(newEntry) = book;
|
static_cast<Book&>(newEntry) = book;
|
||||||
newEntry.lastUpdatedRevision = m_revision;
|
newEntry.lastUpdatedRevision = mp_impl->m_revision;
|
||||||
size_t new_cache_size = static_cast<size_t>(std::ceil(getBookCount_not_protected(true, true)*0.1));
|
size_t new_cache_size = static_cast<size_t>(std::ceil(mp_impl->getBookCount(true, true)*0.1));
|
||||||
if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) {
|
if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) {
|
||||||
mp_archiveCache->setMaxSize(new_cache_size);
|
mp_impl->mp_archiveCache->setMaxSize(new_cache_size);
|
||||||
}
|
}
|
||||||
if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) {
|
if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) {
|
||||||
mp_searcherCache->setMaxSize(new_cache_size);
|
mp_impl->mp_searcherCache->setMaxSize(new_cache_size);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -141,186 +187,33 @@ bool Library::addBook(const Book& book)
|
|||||||
|
|
||||||
void Library::addBookmark(const Bookmark& bookmark)
|
void Library::addBookmark(const Bookmark& bookmark)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_bookmarks.push_back(bookmark);
|
mp_impl->m_bookmarks.push_back(bookmark);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) {
|
for(auto it=mp_impl->m_bookmarks.begin(); it!=mp_impl->m_bookmarks.end(); it++) {
|
||||||
if (it->getBookId() == zimId && it->getUrl() == url) {
|
if (it->getBookId() == zimId && it->getUrl() == url) {
|
||||||
m_bookmarks.erase(it);
|
mp_impl->m_bookmarks.erase(it);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<int, int> Library::migrateBookmarks(MigrationMode migrationMode) {
|
|
||||||
std::set<std::string> sourceBooks;
|
|
||||||
int invalidBookmarks = 0;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
|
||||||
for(auto& bookmark:m_bookmarks) {
|
|
||||||
if (m_books.find(bookmark.getBookId()) == m_books.end()) {
|
|
||||||
invalidBookmarks += 1;
|
|
||||||
sourceBooks.insert(bookmark.getBookId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int changed = 0;
|
|
||||||
for(auto& sourceBook:sourceBooks) {
|
|
||||||
changed += migrateBookmarks(sourceBook, migrationMode);
|
|
||||||
}
|
|
||||||
return std::make_tuple(changed, invalidBookmarks);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Library::getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const {
|
|
||||||
// This function try to get the best book for a bookmark from a book collection.
|
|
||||||
// It assumes that all books in the collection are "acceptable".
|
|
||||||
// (this definiton is not clear but for now it is book's name is equal to bookmark's bookName)
|
|
||||||
//
|
|
||||||
// The algorithm first sort the colletion by "flavour equality" and date.
|
|
||||||
// "flavour equality" is if book's flavour is same that bookmark's flavour (let's say "flavourA" here)
|
|
||||||
// So we have the sorted collection:
|
|
||||||
// - flavourA, date 5
|
|
||||||
// - flavourA, date 4
|
|
||||||
// - flavourB, date 6
|
|
||||||
// - flavourC, date 5
|
|
||||||
// - flavourB, date 3
|
|
||||||
//
|
|
||||||
// Then, depending of migrationMode:
|
|
||||||
// - If ALLOW_DOWNGRADE => take the first one
|
|
||||||
// - If UPGRADE_ONLY => loop on books until we find a book newer than bookmark.
|
|
||||||
// So if bookmark date is 5 => flavourB, date 6
|
|
||||||
// if bookmark date is 4 => flavourA, date 5
|
|
||||||
// if bookmark date is 7 => No book
|
|
||||||
|
|
||||||
if (books.empty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
sort(books, DATE, false);
|
|
||||||
stable_sort(books.begin(), books.end(), [&](const std::string& bookId1, const std::string& bookId2) {
|
|
||||||
const auto& book1 = getBookById(bookId1);
|
|
||||||
const auto& book2 = getBookById(bookId2);
|
|
||||||
bool same_flavour1 = book1.getFlavour() == bookmark.getBookFlavour();
|
|
||||||
bool same_flavour2 = book2.getFlavour() == bookmark.getBookFlavour();
|
|
||||||
// return True if bookId1 is before bookId2, ie if same_flavour1 and not same_flavour2
|
|
||||||
return same_flavour1 > same_flavour2;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (migrationMode == ALLOW_DOWNGRADE) {
|
|
||||||
return books[0];
|
|
||||||
} else {
|
|
||||||
for (const auto& bookId: books) {
|
|
||||||
const auto& book = getBookById(bookId);
|
|
||||||
if (book.getDate() >= bookmark.getDate()) {
|
|
||||||
return bookId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string remove_quote(std::string input) {
|
|
||||||
std::replace(input.begin(), input.end(), '"', ' ');
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Library::getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour, const std::string& minDate) const {
|
|
||||||
// Let's reuse our algorithm based on bookmark.
|
|
||||||
MigrationMode migrationMode = UPGRADE_ONLY;
|
|
||||||
auto bookmark = Bookmark();
|
|
||||||
bookmark.setBookName(bookName);
|
|
||||||
bookmark.setBookFlavour(preferedFlavour);
|
|
||||||
|
|
||||||
if (minDate.empty()) {
|
|
||||||
migrationMode = ALLOW_DOWNGRADE;
|
|
||||||
} else {
|
|
||||||
bookmark.setDate(minDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getBestTargetBookId(bookmark, migrationMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Library::getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const {
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
|
||||||
// Search for a existing book with the same name
|
|
||||||
auto book_filter = Filter();
|
|
||||||
if (!bookmark.getBookName().empty()) {
|
|
||||||
book_filter.name(bookmark.getBookName());
|
|
||||||
} else {
|
|
||||||
// We don't have a name stored (older bookmarks)
|
|
||||||
// Fallback on title (All bookmarks should have one, but let's be safe against wrongly filled bookmark)
|
|
||||||
if (bookmark.getBookTitle().empty()) {
|
|
||||||
// No bookName nor bookTitle, no way to find target book.
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
book_filter.query("title:\"" + remove_quote(bookmark.getBookTitle()) + "\"");
|
|
||||||
}
|
|
||||||
auto targetBooks = filter(book_filter);
|
|
||||||
auto bestBook = getBestFromBookCollection(targetBooks, bookmark, migrationMode);
|
|
||||||
if (bestBook.empty()) {
|
|
||||||
try {
|
|
||||||
getBookById(bookmark.getBookId());
|
|
||||||
return bookmark.getBookId();
|
|
||||||
} catch (std::out_of_range&) {}
|
|
||||||
}
|
|
||||||
return bestBook;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Library::migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode) {
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
|
||||||
|
|
||||||
Bookmark firstBookmarkToChange;
|
|
||||||
for(auto& bookmark:m_bookmarks) {
|
|
||||||
if (bookmark.getBookId() == sourceBookId) {
|
|
||||||
firstBookmarkToChange = bookmark;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstBookmarkToChange.getBookId().empty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string betterBook = getBestTargetBookId(firstBookmarkToChange, migrationMode);
|
|
||||||
|
|
||||||
if (betterBook.empty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return migrateBookmarks(sourceBookId, betterBook);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Library::migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId) {
|
|
||||||
if (sourceBookId == targetBookId) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int changed = 0;
|
|
||||||
for (auto& bookmark:m_bookmarks) {
|
|
||||||
if (bookmark.getBookId() == sourceBookId) {
|
|
||||||
bookmark.setBookId(targetBookId);
|
|
||||||
changed +=1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Library::dropCache(const std::string& id)
|
void Library::dropCache(const std::string& id)
|
||||||
{
|
{
|
||||||
mp_archiveCache->drop(id);
|
mp_impl->mp_archiveCache->drop(id);
|
||||||
mp_searcherCache->drop(id);
|
mp_impl->mp_searcherCache->drop(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Library::removeBookById(const std::string& id)
|
bool Library::removeBookById(const std::string& id)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
m_bookDB->delete_document("Q" + id);
|
mp_impl->m_bookDB.delete_document("Q" + id);
|
||||||
dropCache(id);
|
dropCache(id);
|
||||||
// We do not change the cache size here
|
// We do not change the cache size here
|
||||||
// Most of the time, the book is remove in case of library refresh, it is
|
// Most of the time, the book is remove in case of library refresh, it is
|
||||||
@@ -328,25 +221,25 @@ bool Library::removeBookById(const std::string& id)
|
|||||||
// Having a too big cache is not a problem here (or it would have been before)
|
// Having a too big cache is not a problem here (or it would have been before)
|
||||||
// (And setMaxSize doesn't actually reduce the cache size, extra cached items
|
// (And setMaxSize doesn't actually reduce the cache size, extra cached items
|
||||||
// will be removed in put or getOrPut).
|
// will be removed in put or getOrPut).
|
||||||
const bool bookWasRemoved = m_books.erase(id) == 1;
|
const bool bookWasRemoved = mp_impl->m_books.erase(id) == 1;
|
||||||
if ( bookWasRemoved ) {
|
if ( bookWasRemoved ) {
|
||||||
++m_revision;
|
++mp_impl->m_revision;
|
||||||
}
|
}
|
||||||
return bookWasRemoved;
|
return bookWasRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
Library::Revision Library::getRevision() const
|
Library::Revision Library::getRevision() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
return m_revision;
|
return mp_impl->m_revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
|
uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
|
||||||
{
|
{
|
||||||
BookIdCollection booksToRemove;
|
BookIdCollection booksToRemove;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
for ( const auto& entry : m_books) {
|
for ( const auto& entry : mp_impl->m_books) {
|
||||||
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
|
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
|
||||||
booksToRemove.push_back(entry.first);
|
booksToRemove.push_back(entry.first);
|
||||||
}
|
}
|
||||||
@@ -365,12 +258,12 @@ const Book& Library::getBookById(const std::string& id) const
|
|||||||
{
|
{
|
||||||
// XXX: Doesn't make sense to lock this operation since it cannot
|
// XXX: Doesn't make sense to lock this operation since it cannot
|
||||||
// XXX: guarantee thread-safety because of its return type
|
// XXX: guarantee thread-safety because of its return type
|
||||||
return m_books.at(id);
|
return mp_impl->m_books.at(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Book Library::getBookByIdThreadSafe(const std::string& id) const
|
Book Library::getBookByIdThreadSafe(const std::string& id) const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
return getBookById(id);
|
return getBookById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +271,7 @@ const Book& Library::getBookByPath(const std::string& path) const
|
|||||||
{
|
{
|
||||||
// XXX: Doesn't make sense to lock this operation since it cannot
|
// XXX: Doesn't make sense to lock this operation since it cannot
|
||||||
// XXX: guarantee thread-safety because of its return type
|
// XXX: guarantee thread-safety because of its return type
|
||||||
for(auto& it: m_books) {
|
for(auto& it: mp_impl->m_books) {
|
||||||
auto& book = it.second;
|
auto& book = it.second;
|
||||||
if (book.getPath() == path)
|
if (book.getPath() == path)
|
||||||
return book;
|
return book;
|
||||||
@@ -391,7 +284,7 @@ const Book& Library::getBookByPath(const std::string& path) const
|
|||||||
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
|
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return mp_archiveCache->getOrPut(id,
|
return mp_impl->mp_archiveCache->getOrPut(id,
|
||||||
[&](){
|
[&](){
|
||||||
auto book = getBookById(id);
|
auto book = getBookById(id);
|
||||||
if (!book.isPathValid()) {
|
if (!book.isPathValid()) {
|
||||||
@@ -408,7 +301,7 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
|
|||||||
{
|
{
|
||||||
assert(!ids.empty());
|
assert(!ids.empty());
|
||||||
try {
|
try {
|
||||||
return mp_searcherCache->getOrPut(ids,
|
return mp_impl->mp_searcherCache->getOrPut(ids,
|
||||||
[&](){
|
[&](){
|
||||||
std::vector<zim::Archive> archives;
|
std::vector<zim::Archive> archives;
|
||||||
for(auto& id:ids) {
|
for(auto& id:ids) {
|
||||||
@@ -428,8 +321,8 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
|
|||||||
unsigned int Library::getBookCount(const bool localBooks,
|
unsigned int Library::getBookCount(const bool localBooks,
|
||||||
const bool remoteBooks) const
|
const bool remoteBooks) const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
return getBookCount_not_protected(localBooks, remoteBooks);
|
return mp_impl->getBookCount(localBooks, remoteBooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Library::writeToFile(const std::string& path) const
|
bool Library::writeToFile(const std::string& path) const
|
||||||
@@ -441,7 +334,7 @@ bool Library::writeToFile(const std::string& path) const
|
|||||||
dumper.setBaseDir(baseDir);
|
dumper.setBaseDir(baseDir);
|
||||||
std::string xml;
|
std::string xml;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
xml = dumper.dumpLibXMLContent(allBookIds);
|
xml = dumper.dumpLibXMLContent(allBookIds);
|
||||||
};
|
};
|
||||||
return writeTextFile(path, xml);
|
return writeTextFile(path, xml);
|
||||||
@@ -457,10 +350,10 @@ bool Library::writeBookmarksToFile(const std::string& path) const
|
|||||||
|
|
||||||
Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const
|
Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
AttributeCounts propValueCounts;
|
AttributeCounts propValueCounts;
|
||||||
|
|
||||||
for (const auto& pair: m_books) {
|
for (const auto& pair: mp_impl->m_books) {
|
||||||
const auto& book = pair.second;
|
const auto& book = pair.second;
|
||||||
if (book.getOrigId().empty()) {
|
if (book.getOrigId().empty()) {
|
||||||
propValueCounts[(book.*p)()] += 1;
|
propValueCounts[(book.*p)()] += 1;
|
||||||
@@ -489,10 +382,10 @@ std::vector<std::string> Library::getBooksLanguages() const
|
|||||||
|
|
||||||
Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
AttributeCounts langsWithCounts;
|
AttributeCounts langsWithCounts;
|
||||||
|
|
||||||
for (const auto& pair: m_books) {
|
for (const auto& pair: mp_impl->m_books) {
|
||||||
const auto& book = pair.second;
|
const auto& book = pair.second;
|
||||||
if (book.getOrigId().empty()) {
|
if (book.getOrigId().empty()) {
|
||||||
for ( const auto& lang : book.getLanguages() ) {
|
for ( const auto& lang : book.getLanguages() ) {
|
||||||
@@ -505,10 +398,10 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
|||||||
|
|
||||||
std::vector<std::string> Library::getBooksCategories() const
|
std::vector<std::string> Library::getBooksCategories() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
std::set<std::string> categories;
|
std::set<std::string> categories;
|
||||||
|
|
||||||
for (const auto& pair: m_books) {
|
for (const auto& pair: mp_impl->m_books) {
|
||||||
const auto& book = pair.second;
|
const auto& book = pair.second;
|
||||||
const auto& c = book.getCategory();
|
const auto& c = book.getCategory();
|
||||||
if ( !c.empty() ) {
|
if ( !c.empty() ) {
|
||||||
@@ -532,12 +425,12 @@ std::vector<std::string> Library::getBooksPublishers() const
|
|||||||
const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const
|
const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const
|
||||||
{
|
{
|
||||||
if (!onlyValidBookmarks) {
|
if (!onlyValidBookmarks) {
|
||||||
return m_bookmarks;
|
return mp_impl->m_bookmarks;
|
||||||
}
|
}
|
||||||
std::vector<kiwix::Bookmark> validBookmarks;
|
std::vector<kiwix::Bookmark> validBookmarks;
|
||||||
auto booksId = getBooksIds();
|
auto booksId = getBooksIds();
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
for(auto& bookmark:m_bookmarks) {
|
for(auto& bookmark:mp_impl->m_bookmarks) {
|
||||||
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
|
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
|
||||||
validBookmarks.push_back(bookmark);
|
validBookmarks.push_back(bookmark);
|
||||||
}
|
}
|
||||||
@@ -547,10 +440,10 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
|||||||
|
|
||||||
Library::BookIdCollection Library::getBooksIds() const
|
Library::BookIdCollection Library::getBooksIds() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
BookIdCollection bookIds;
|
BookIdCollection bookIds;
|
||||||
|
|
||||||
for (auto& pair: m_books) {
|
for (auto& pair: mp_impl->m_books) {
|
||||||
bookIds.push_back(pair.first);
|
bookIds.push_back(pair.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,8 +482,7 @@ void Library::updateBookDB(const Book& book)
|
|||||||
}
|
}
|
||||||
indexer.index_text(normalizeText(book.getCreator()), 1, "A");
|
indexer.index_text(normalizeText(book.getCreator()), 1, "A");
|
||||||
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
|
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
|
||||||
doc.add_term("XN"+normalizeText(book.getName()));
|
indexer.index_text(normalizeText(book.getName()), 1, "XN");
|
||||||
indexer.index_text(normalizeText(book.getFlavour()), 1, "XF");
|
|
||||||
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
|
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
|
||||||
|
|
||||||
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
|
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
|
||||||
@@ -606,7 +498,7 @@ void Library::updateBookDB(const Book& book)
|
|||||||
|
|
||||||
doc.set_data(book.getId());
|
doc.set_data(book.getId());
|
||||||
|
|
||||||
m_bookDB->replace_document(idterm, doc);
|
mp_impl->m_bookDB.replace_document(idterm, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -631,7 +523,6 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
|
|||||||
queryParser.add_prefix("title", "S");
|
queryParser.add_prefix("title", "S");
|
||||||
queryParser.add_prefix("description", "XD");
|
queryParser.add_prefix("description", "XD");
|
||||||
queryParser.add_prefix("name", "XN");
|
queryParser.add_prefix("name", "XN");
|
||||||
queryParser.add_prefix("flavour", "XF");
|
|
||||||
queryParser.add_prefix("category", "XC");
|
queryParser.add_prefix("category", "XC");
|
||||||
queryParser.add_prefix("lang", "L");
|
queryParser.add_prefix("lang", "L");
|
||||||
queryParser.add_prefix("publisher", "XP");
|
queryParser.add_prefix("publisher", "XP");
|
||||||
@@ -645,6 +536,8 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
|
|||||||
//queryParser.set_stemmer(Xapian::Stem(iso639_3ToXapian(???)));
|
//queryParser.set_stemmer(Xapian::Stem(iso639_3ToXapian(???)));
|
||||||
//queryParser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
|
//queryParser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
|
||||||
const auto flags = Xapian::QueryParser::FLAG_PHRASE
|
const auto flags = Xapian::QueryParser::FLAG_PHRASE
|
||||||
|
| Xapian::QueryParser::FLAG_BOOLEAN
|
||||||
|
| Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE
|
||||||
| Xapian::QueryParser::FLAG_LOVEHATE
|
| Xapian::QueryParser::FLAG_LOVEHATE
|
||||||
| Xapian::QueryParser::FLAG_WILDCARD
|
| Xapian::QueryParser::FLAG_WILDCARD
|
||||||
| partialQueryFlag;
|
| partialQueryFlag;
|
||||||
@@ -656,35 +549,25 @@ Xapian::Query nameQuery(const std::string& name)
|
|||||||
return Xapian::Query("XN" + normalizeText(name));
|
return Xapian::Query("XN" + normalizeText(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
Xapian::Query flavourQuery(const std::string& name)
|
Xapian::Query categoryQuery(const std::string& category)
|
||||||
{
|
{
|
||||||
return Xapian::Query("XF" + normalizeText(name));
|
return Xapian::Query("XC" + normalizeText(category));
|
||||||
}
|
|
||||||
|
|
||||||
Xapian::Query multipleParamQuery(const std::string& commaSeparatedList, const std::string& prefix)
|
|
||||||
{
|
|
||||||
Xapian::Query q;
|
|
||||||
bool firstIteration = true;
|
|
||||||
for ( const auto& elem : kiwix::split(commaSeparatedList, ",") ) {
|
|
||||||
const Xapian::Query singleQuery(prefix + normalizeText(elem));
|
|
||||||
if ( firstIteration ) {
|
|
||||||
q = singleQuery;
|
|
||||||
firstIteration = false;
|
|
||||||
} else {
|
|
||||||
q = Xapian::Query(Xapian::Query::OP_OR, q, singleQuery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
Xapian::Query categoryQuery(const std::string& commaSeparatedCategoryList)
|
|
||||||
{
|
|
||||||
return multipleParamQuery(commaSeparatedCategoryList, "XC");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Xapian::Query langQuery(const std::string& commaSeparatedLanguageList)
|
Xapian::Query langQuery(const std::string& commaSeparatedLanguageList)
|
||||||
{
|
{
|
||||||
return multipleParamQuery(commaSeparatedLanguageList, "L");
|
Xapian::Query q;
|
||||||
|
bool firstIteration = true;
|
||||||
|
for ( const auto& lang : kiwix::split(commaSeparatedLanguageList, ",") ) {
|
||||||
|
const Xapian::Query singleLangQuery("L" + normalizeText(lang));
|
||||||
|
if ( firstIteration ) {
|
||||||
|
q = singleLangQuery;
|
||||||
|
firstIteration = false;
|
||||||
|
} else {
|
||||||
|
q = Xapian::Query(Xapian::Query::OP_OR, q, singleLangQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
Xapian::Query publisherQuery(const std::string& publisher)
|
Xapian::Query publisherQuery(const std::string& publisher)
|
||||||
@@ -728,9 +611,6 @@ Xapian::Query buildXapianQuery(const Filter& filter)
|
|||||||
if ( filter.hasName() ) {
|
if ( filter.hasName() ) {
|
||||||
q = Xapian::Query(Xapian::Query::OP_AND, q, nameQuery(filter.getName()));
|
q = Xapian::Query(Xapian::Query::OP_AND, q, nameQuery(filter.getName()));
|
||||||
}
|
}
|
||||||
if ( filter.hasFlavour() ) {
|
|
||||||
q = Xapian::Query(Xapian::Query::OP_AND, q, flavourQuery(filter.getFlavour()));
|
|
||||||
}
|
|
||||||
if ( filter.hasCategory() ) {
|
if ( filter.hasCategory() ) {
|
||||||
q = Xapian::Query(Xapian::Query::OP_AND, q, categoryQuery(filter.getCategory()));
|
q = Xapian::Query(Xapian::Query::OP_AND, q, categoryQuery(filter.getCategory()));
|
||||||
}
|
}
|
||||||
@@ -761,10 +641,10 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
|
|||||||
|
|
||||||
BookIdCollection bookIds;
|
BookIdCollection bookIds;
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
Xapian::Enquire enquire(*m_bookDB);
|
Xapian::Enquire enquire(mp_impl->m_bookDB);
|
||||||
enquire.set_query(query);
|
enquire.set_query(query);
|
||||||
const auto results = enquire.get_mset(0, m_books.size());
|
const auto results = enquire.get_mset(0, mp_impl->m_books.size());
|
||||||
for ( auto it = results.begin(); it != results.end(); ++it ) {
|
for ( auto it = results.begin(); it != results.end(); ++it ) {
|
||||||
bookIds.push_back(it.get_document().get_data());
|
bookIds.push_back(it.get_document().get_data());
|
||||||
}
|
}
|
||||||
@@ -776,9 +656,9 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
|
|||||||
{
|
{
|
||||||
BookIdCollection result;
|
BookIdCollection result;
|
||||||
const auto preliminaryResult = filterViaBookDB(filter);
|
const auto preliminaryResult = filterViaBookDB(filter);
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
for(auto id : preliminaryResult) {
|
for(auto id : preliminaryResult) {
|
||||||
if(filter.accept(m_books.at(id))) {
|
if(filter.accept(mp_impl->m_books.at(id))) {
|
||||||
result.push_back(id);
|
result.push_back(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -850,7 +730,7 @@ void Library::sort(BookIdCollection& bookIds, supportedListSortBy sort, bool asc
|
|||||||
// NOTE: for the entire duration of the sort. Will need to obtain (under a
|
// NOTE: for the entire duration of the sort. Will need to obtain (under a
|
||||||
// NOTE: lock) the required atributes from the books once, and then the
|
// NOTE: lock) the required atributes from the books once, and then the
|
||||||
// NOTE: sorting will run on a copy of data without locking.
|
// NOTE: sorting will run on a copy of data without locking.
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
switch(sort) {
|
switch(sort) {
|
||||||
case TITLE:
|
case TITLE:
|
||||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
|
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
|
||||||
@@ -896,7 +776,6 @@ enum filterTypes {
|
|||||||
QUERY = FLAG(12),
|
QUERY = FLAG(12),
|
||||||
NAME = FLAG(13),
|
NAME = FLAG(13),
|
||||||
CATEGORY = FLAG(14),
|
CATEGORY = FLAG(14),
|
||||||
FLAVOUR = FLAG(15),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Filter& Filter::local(bool accept)
|
Filter& Filter::local(bool accept)
|
||||||
@@ -998,13 +877,6 @@ Filter& Filter::name(std::string name)
|
|||||||
activeFilters |= NAME;
|
activeFilters |= NAME;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Filter& Filter::flavour(std::string flavour)
|
|
||||||
{
|
|
||||||
_flavour = flavour;
|
|
||||||
activeFilters |= FLAVOUR;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Filter& Filter::clearLang()
|
Filter& Filter::clearLang()
|
||||||
{
|
{
|
||||||
@@ -1050,12 +922,6 @@ bool Filter::hasCreator() const
|
|||||||
return ACTIVE(_CREATOR);
|
return ACTIVE(_CREATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Filter::hasFlavour() const
|
|
||||||
{
|
|
||||||
return ACTIVE(FLAVOUR);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool Filter::accept(const Book& book) const
|
bool Filter::accept(const Book& book) const
|
||||||
{
|
{
|
||||||
auto local = !book.getPath().empty();
|
auto local = !book.getPath().empty();
|
||||||
|
|||||||
@@ -23,6 +23,64 @@ void LibraryDumper::setOpenSearchInfo(int totalResults, int startIndex, int coun
|
|||||||
m_count = count;
|
m_count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::map<std::string, std::string> iso639_3 = {
|
||||||
|
{"atj", "atikamekw"},
|
||||||
|
{"azb", "آذربایجان دیلی"},
|
||||||
|
{"bcl", "central bikol"},
|
||||||
|
{"bgs", "tagabawa"},
|
||||||
|
{"bxr", "буряад хэлэн"},
|
||||||
|
{"cbk", "chavacano"},
|
||||||
|
{"cdo", "閩東語"},
|
||||||
|
{"dag", "Dagbani"},
|
||||||
|
{"diq", "dimli"},
|
||||||
|
{"dty", "डोटेली"},
|
||||||
|
{"eml", "emiliân-rumagnōl"},
|
||||||
|
{"fbs", "српскохрватски"},
|
||||||
|
{"hbs", "srpskohrvatski"},
|
||||||
|
{"ido", "ido"},
|
||||||
|
{"kbp", "kabɩyɛ"},
|
||||||
|
{"kld", "Gamilaraay"},
|
||||||
|
{"lbe", "лакку маз"},
|
||||||
|
{"lbj", "ལ་དྭགས་སྐད་"},
|
||||||
|
{"map", "Austronesian"},
|
||||||
|
{"mhr", "марий йылме"},
|
||||||
|
{"mnw", "ဘာသာမန်"},
|
||||||
|
{"myn", "mayan"},
|
||||||
|
{"nah", "nahuatl"},
|
||||||
|
{"nai", "north American Indian"},
|
||||||
|
{"nds", "plattdütsch"},
|
||||||
|
{"nrm", "bhasa narom"},
|
||||||
|
{"olo", "livvi"},
|
||||||
|
{"pih", "Pitcairn-Norfolk"},
|
||||||
|
{"pnb", "Western Panjabi"},
|
||||||
|
{"rmr", "Caló"},
|
||||||
|
{"rmy", "romani shib"},
|
||||||
|
{"roa", "romance languages"},
|
||||||
|
{"twi", "twi"},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::once_flag fillLanguagesFlag;
|
||||||
|
|
||||||
|
void fillLanguagesMap()
|
||||||
|
{
|
||||||
|
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
|
||||||
|
const ICULanguageInfo lang(*icuLangPtr);
|
||||||
|
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getLanguageSelfName(const std::string& lang) {
|
||||||
|
const auto itr = iso639_3.find(lang);
|
||||||
|
if (itr != iso639_3.end()) {
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
return lang;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
kainjow::mustache::list LibraryDumper::getCategoryData() const
|
kainjow::mustache::list LibraryDumper::getCategoryData() const
|
||||||
{
|
{
|
||||||
const auto now = gen_date_str();
|
const auto now = gen_date_str();
|
||||||
@@ -43,6 +101,7 @@ kainjow::mustache::list LibraryDumper::getLanguageData() const
|
|||||||
{
|
{
|
||||||
const auto now = gen_date_str();
|
const auto now = gen_date_str();
|
||||||
kainjow::mustache::list languageData;
|
kainjow::mustache::list languageData;
|
||||||
|
std::call_once(fillLanguagesFlag, fillLanguagesMap);
|
||||||
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
||||||
const std::string languageCode = langAndBookCount.first;
|
const std::string languageCode = langAndBookCount.first;
|
||||||
const int bookCount = langAndBookCount.second;
|
const int bookCount = langAndBookCount.second;
|
||||||
|
|||||||
@@ -97,15 +97,11 @@ void LibXMLDumper::handleBookmark(Bookmark bookmark, pugi::xml_node root_node) {
|
|||||||
auto book = library->getBookByIdThreadSafe(bookmark.getBookId());
|
auto book = library->getBookByIdThreadSafe(bookmark.getBookId());
|
||||||
ADD_TEXT_ENTRY(book_node, "id", book.getId());
|
ADD_TEXT_ENTRY(book_node, "id", book.getId());
|
||||||
ADD_TEXT_ENTRY(book_node, "title", book.getTitle());
|
ADD_TEXT_ENTRY(book_node, "title", book.getTitle());
|
||||||
ADD_TEXT_ENTRY(book_node, "name", book.getName());
|
|
||||||
ADD_TEXT_ENTRY(book_node, "flavour", book.getFlavour());
|
|
||||||
ADD_TEXT_ENTRY(book_node, "language", book.getCommaSeparatedLanguages());
|
ADD_TEXT_ENTRY(book_node, "language", book.getCommaSeparatedLanguages());
|
||||||
ADD_TEXT_ENTRY(book_node, "date", book.getDate());
|
ADD_TEXT_ENTRY(book_node, "date", book.getDate());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ADD_TEXT_ENTRY(book_node, "id", bookmark.getBookId());
|
ADD_TEXT_ENTRY(book_node, "id", bookmark.getBookId());
|
||||||
ADD_TEXT_ENTRY(book_node, "title", bookmark.getBookTitle());
|
ADD_TEXT_ENTRY(book_node, "title", bookmark.getBookTitle());
|
||||||
ADD_TEXT_ENTRY(book_node, "name", bookmark.getBookName());
|
|
||||||
ADD_TEXT_ENTRY(book_node, "flavour", bookmark.getBookFlavour());
|
|
||||||
ADD_TEXT_ENTRY(book_node, "language", bookmark.getLanguage());
|
ADD_TEXT_ENTRY(book_node, "language", bookmark.getLanguage());
|
||||||
ADD_TEXT_ENTRY(book_node, "date", bookmark.getDate());
|
ADD_TEXT_ENTRY(book_node, "date", bookmark.getDate());
|
||||||
}
|
}
|
||||||
@@ -139,7 +135,7 @@ std::string LibXMLDumper::dumpLibXMLBookmark()
|
|||||||
pugi::xml_node bookmarksNode = doc.append_child("bookmarks");
|
pugi::xml_node bookmarksNode = doc.append_child("bookmarks");
|
||||||
|
|
||||||
if (library) {
|
if (library) {
|
||||||
for (auto& bookmark: library->getBookmarks(false)) {
|
for (auto& bookmark: library->getBookmarks()) {
|
||||||
handleBookmark(bookmark, bookmarksNode);
|
handleBookmark(bookmark, bookmarksNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-15
@@ -27,12 +27,22 @@
|
|||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
struct NoDelete
|
||||||
|
{
|
||||||
|
template<class T> void operator()(T*) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // unnamed namespace
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// LibraryManipulator
|
// LibraryManipulator
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
LibraryManipulator::LibraryManipulator(LibraryPtr library)
|
LibraryManipulator::LibraryManipulator(Library* library)
|
||||||
: library(library)
|
: library(*library)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
LibraryManipulator::~LibraryManipulator()
|
LibraryManipulator::~LibraryManipulator()
|
||||||
@@ -40,7 +50,7 @@ LibraryManipulator::~LibraryManipulator()
|
|||||||
|
|
||||||
bool LibraryManipulator::addBookToLibrary(const Book& book)
|
bool LibraryManipulator::addBookToLibrary(const Book& book)
|
||||||
{
|
{
|
||||||
const auto ret = library->addBook(book);
|
const auto ret = library.addBook(book);
|
||||||
if ( ret ) {
|
if ( ret ) {
|
||||||
bookWasAddedToLibrary(book);
|
bookWasAddedToLibrary(book);
|
||||||
}
|
}
|
||||||
@@ -49,13 +59,13 @@ bool LibraryManipulator::addBookToLibrary(const Book& book)
|
|||||||
|
|
||||||
void LibraryManipulator::addBookmarkToLibrary(const Bookmark& bookmark)
|
void LibraryManipulator::addBookmarkToLibrary(const Bookmark& bookmark)
|
||||||
{
|
{
|
||||||
library->addBookmark(bookmark);
|
library.addBookmark(bookmark);
|
||||||
bookmarkWasAddedToLibrary(bookmark);
|
bookmarkWasAddedToLibrary(bookmark);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t LibraryManipulator::removeBooksNotUpdatedSince(Library::Revision rev)
|
uint32_t LibraryManipulator::removeBooksNotUpdatedSince(Library::Revision rev)
|
||||||
{
|
{
|
||||||
const auto n = library->removeBooksNotUpdatedSince(rev);
|
const auto n = library.removeBooksNotUpdatedSince(rev);
|
||||||
if ( n != 0 ) {
|
if ( n != 0 ) {
|
||||||
booksWereRemovedFromLibrary();
|
booksWereRemovedFromLibrary();
|
||||||
}
|
}
|
||||||
@@ -79,15 +89,15 @@ void LibraryManipulator::booksWereRemovedFromLibrary()
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
Manager::Manager(LibraryManipulator manipulator):
|
Manager::Manager(LibraryManipulator* manipulator):
|
||||||
writableLibraryPath(""),
|
writableLibraryPath(""),
|
||||||
manipulator(manipulator)
|
manipulator(manipulator, NoDelete())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::Manager(LibraryPtr library) :
|
Manager::Manager(Library* library) :
|
||||||
writableLibraryPath(""),
|
writableLibraryPath(""),
|
||||||
manipulator(LibraryManipulator(library))
|
manipulator(new LibraryManipulator(library))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +121,7 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
|||||||
if (!trustLibrary && !book.getPath().empty()) {
|
if (!trustLibrary && !book.getPath().empty()) {
|
||||||
this->readBookFromPath(book.getPath(), &book);
|
this->readBookFromPath(book.getPath(), &book);
|
||||||
}
|
}
|
||||||
manipulator.addBookToLibrary(book);
|
manipulator->addBookToLibrary(book);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -156,7 +166,7 @@ bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& url
|
|||||||
book.updateFromOpds(entryNode, urlHost);
|
book.updateFromOpds(entryNode, urlHost);
|
||||||
|
|
||||||
/* Update the book properties with the new importer */
|
/* Update the book properties with the new importer */
|
||||||
manipulator.addBookToLibrary(book);
|
manipulator->addBookToLibrary(book);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -231,7 +241,7 @@ std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
|
|||||||
|| (!book.getTitle().empty() && !book.getLanguages().empty()
|
|| (!book.getTitle().empty() && !book.getLanguages().empty()
|
||||||
&& !book.getDate().empty())) {
|
&& !book.getDate().empty())) {
|
||||||
book.setUrl(url);
|
book.setUrl(url);
|
||||||
manipulator.addBookToLibrary(book);
|
manipulator->addBookToLibrary(book);
|
||||||
return book.getId();
|
return book.getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +296,7 @@ bool Manager::readBookmarkFile(const std::string& path)
|
|||||||
|
|
||||||
bookmark.updateFromXml(node);
|
bookmark.updateFromXml(node);
|
||||||
|
|
||||||
manipulator.addBookmarkToLibrary(bookmark);
|
manipulator->addBookmarkToLibrary(bookmark);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -294,7 +304,7 @@ bool Manager::readBookmarkFile(const std::string& path)
|
|||||||
|
|
||||||
void Manager::reload(const Paths& paths)
|
void Manager::reload(const Paths& paths)
|
||||||
{
|
{
|
||||||
const auto libRevision = manipulator.getLibrary()->getRevision();
|
const auto libRevision = manipulator->getLibrary().getRevision();
|
||||||
for (std::string path : paths) {
|
for (std::string path : paths) {
|
||||||
if (!path.empty()) {
|
if (!path.empty()) {
|
||||||
if ( kiwix::isRelativePath(path) )
|
if ( kiwix::isRelativePath(path) )
|
||||||
@@ -306,7 +316,7 @@ void Manager::reload(const Paths& paths)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manipulator.removeBooksNotUpdatedSince(libRevision);
|
manipulator->removeBooksNotUpdatedSince(libRevision);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,6 @@ kiwix_sources = [
|
|||||||
'tools/regexTools.cpp',
|
'tools/regexTools.cpp',
|
||||||
'tools/stringTools.cpp',
|
'tools/stringTools.cpp',
|
||||||
'tools/networkTools.cpp',
|
'tools/networkTools.cpp',
|
||||||
'tools/opdsParsingTools.cpp',
|
|
||||||
'tools/languageTools.cpp',
|
|
||||||
'tools/otherTools.cpp',
|
'tools/otherTools.cpp',
|
||||||
'tools/archiveTools.cpp',
|
'tools/archiveTools.cpp',
|
||||||
'kiwixserve.cpp',
|
'kiwixserve.cpp',
|
||||||
|
|||||||
File renamed without changes.
+15
-19
@@ -29,29 +29,25 @@ HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool w
|
|||||||
auto& currentBook = library.getBookById(bookId);
|
auto& currentBook = library.getBookById(bookId);
|
||||||
auto bookName = currentBook.getHumanReadableIdFromPath();
|
auto bookName = currentBook.getHumanReadableIdFromPath();
|
||||||
m_idToName[bookId] = bookName;
|
m_idToName[bookId] = bookName;
|
||||||
mapName(library, bookName, bookId);
|
m_nameToId[bookName] = bookId;
|
||||||
|
|
||||||
if (!withAlias)
|
if (!withAlias)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
|
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
|
||||||
if (aliasName != bookName) {
|
if (aliasName == bookName) {
|
||||||
mapName(library, aliasName, bookId);
|
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;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +63,7 @@ std::string HumanReadableNameMapper::getIdForName(const std::string& name) const
|
|||||||
// UpdatableNameMapper
|
// UpdatableNameMapper
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
UpdatableNameMapper::UpdatableNameMapper(LibraryPtr lib, bool withAlias)
|
UpdatableNameMapper::UpdatableNameMapper(Library& lib, bool withAlias)
|
||||||
: library(lib)
|
: library(lib)
|
||||||
, withAlias(withAlias)
|
, withAlias(withAlias)
|
||||||
{
|
{
|
||||||
@@ -76,7 +72,7 @@ UpdatableNameMapper::UpdatableNameMapper(LibraryPtr lib, bool withAlias)
|
|||||||
|
|
||||||
void UpdatableNameMapper::update()
|
void UpdatableNameMapper::update()
|
||||||
{
|
{
|
||||||
const auto newNameMapper = new HumanReadableNameMapper(*library, withAlias);
|
const auto newNameMapper = new HumanReadableNameMapper(library, withAlias);
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
nameMapper.reset(newNameMapper);
|
nameMapper.reset(newNameMapper);
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-62
@@ -32,46 +32,20 @@
|
|||||||
#include "libkiwix-resources.h"
|
#include "libkiwix-resources.h"
|
||||||
#include "tools/stringTools.h"
|
#include "tools/stringTools.h"
|
||||||
|
|
||||||
#include "server/i18n_utils.h"
|
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
ParameterizedMessage searchResultsPageTitleMsg(const std::string& searchPattern)
|
|
||||||
{
|
|
||||||
return ParameterizedMessage("search-results-page-title",
|
|
||||||
{{"SEARCH_PATTERN", searchPattern}}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ParameterizedMessage searchResultsPageHeaderMsg(const std::string& searchPattern,
|
|
||||||
const kainjow::mustache::data& r)
|
|
||||||
{
|
|
||||||
if ( r.get("count")->string_value() == "0" ) {
|
|
||||||
return ParameterizedMessage("empty-search-results-page-header",
|
|
||||||
{{"SEARCH_PATTERN", searchPattern}}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return ParameterizedMessage("search-results-page-header",
|
|
||||||
{
|
|
||||||
{"SEARCH_PATTERN", searchPattern},
|
|
||||||
{"START", r.get("start")->string_value()},
|
|
||||||
{"END", r.get("end") ->string_value()},
|
|
||||||
{"COUNT", r.get("count")->string_value()},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // unnamed namespace
|
|
||||||
|
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
SearchRenderer::SearchRenderer(zim::SearchResultSet srs,
|
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
|
||||||
|
unsigned int start, unsigned int estimatedResultCount)
|
||||||
|
: SearchRenderer(srs, mapper, nullptr, start, estimatedResultCount)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
|
||||||
unsigned int start, unsigned int estimatedResultCount)
|
unsigned int start, unsigned int estimatedResultCount)
|
||||||
: m_srs(srs),
|
: m_srs(srs),
|
||||||
|
mp_nameMapper(mapper),
|
||||||
|
mp_library(library),
|
||||||
protocolPrefix("zim://"),
|
protocolPrefix("zim://"),
|
||||||
searchProtocolPrefix("search://"),
|
searchProtocolPrefix("search://"),
|
||||||
estimatedResultCount(estimatedResultCount),
|
estimatedResultCount(estimatedResultCount),
|
||||||
@@ -190,7 +164,7 @@ kainjow::mustache::data buildPagination(
|
|||||||
return pagination;
|
return pagination;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const NameMapper& nameMapper, const Library* library)
|
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
|
||||||
{
|
{
|
||||||
const std::string absPathPrefix = protocolPrefix;
|
const std::string absPathPrefix = protocolPrefix;
|
||||||
// Build the results list
|
// Build the results list
|
||||||
@@ -198,25 +172,15 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
|
|||||||
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
|
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
|
||||||
kainjow::mustache::data result;
|
kainjow::mustache::data result;
|
||||||
const std::string zim_id(it.getZimId());
|
const std::string zim_id(it.getZimId());
|
||||||
const auto path = nameMapper.getNameForId(zim_id) + "/" + it.getPath();
|
const auto path = mp_nameMapper->getNameForId(zim_id) + "/" + it.getPath();
|
||||||
result.set("title", it.getTitle());
|
result.set("title", it.getTitle());
|
||||||
result.set("absolutePath", absPathPrefix + urlEncode(path));
|
result.set("absolutePath", absPathPrefix + urlEncode(path));
|
||||||
result.set("snippet", it.getSnippet());
|
result.set("snippet", it.getSnippet());
|
||||||
if (library) {
|
if (mp_library) {
|
||||||
const std::string bookTitle = library->getBookById(zim_id).getTitle();
|
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());
|
||||||
const ParameterizedMessage bookInfoMsg("search-result-book-info",
|
|
||||||
{{"BOOK_TITLE", bookTitle}}
|
|
||||||
);
|
|
||||||
result.set("bookInfo", bookInfoMsg.getText(userlang)); // for HTML
|
|
||||||
result.set("bookTitle", bookTitle); // for XML
|
|
||||||
}
|
}
|
||||||
if (it.getWordCount() >= 0) {
|
if (it.getWordCount() >= 0) {
|
||||||
const auto wordCountStr = kiwix::beautifyInteger(it.getWordCount());
|
result.set("wordCount", kiwix::beautifyInteger(it.getWordCount()));
|
||||||
const ParameterizedMessage wordCountMsg("word-count",
|
|
||||||
{{"COUNT", wordCountStr}}
|
|
||||||
);
|
|
||||||
result.set("wordCountInfo", wordCountMsg.getText(userlang)); // for HTML
|
|
||||||
result.set("wordCount", wordCountStr); // for XML
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push_back(result);
|
items.push_back(result);
|
||||||
@@ -224,6 +188,7 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
|
|||||||
kainjow::mustache::data results;
|
kainjow::mustache::data results;
|
||||||
results.set("items", items);
|
results.set("items", items);
|
||||||
results.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
results.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
||||||
|
results.set("hasResults", estimatedResultCount != 0);
|
||||||
results.set("start", kiwix::beautifyInteger(resultStart));
|
results.set("start", kiwix::beautifyInteger(resultStart));
|
||||||
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength-1, estimatedResultCount)));
|
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength-1, estimatedResultCount)));
|
||||||
|
|
||||||
@@ -240,15 +205,12 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
|
|||||||
searchBookQuery
|
searchBookQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
const auto pageHeaderMsg = searchResultsPageHeaderMsg(searchPattern, results);
|
|
||||||
const kainjow::mustache::object allData{
|
kainjow::mustache::data allData;
|
||||||
{"PAGE_TITLE", searchResultsPageTitleMsg(searchPattern).getText(userlang)},
|
allData.set("searchProtocolPrefix", searchProtocolPrefix);
|
||||||
{"PAGE_HEADER", pageHeaderMsg.getText(userlang)},
|
allData.set("results", results);
|
||||||
{"searchProtocolPrefix", searchProtocolPrefix},
|
allData.set("pagination", pagination);
|
||||||
{"results", results},
|
allData.set("query", query);
|
||||||
{"pagination", pagination},
|
|
||||||
{"query", query},
|
|
||||||
};
|
|
||||||
|
|
||||||
kainjow::mustache::mustache tmpl(tmpl_str);
|
kainjow::mustache::mustache tmpl(tmpl_str);
|
||||||
|
|
||||||
@@ -260,14 +222,14 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SearchRenderer::getHtml(const NameMapper& mapper, const Library* library)
|
std::string SearchRenderer::getHtml()
|
||||||
{
|
{
|
||||||
return renderTemplate(RESOURCE::templates::search_result_html, mapper, library);
|
return renderTemplate(RESOURCE::templates::search_result_html);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SearchRenderer::getXml(const NameMapper& mapper, const Library* library)
|
std::string SearchRenderer::getXml()
|
||||||
{
|
{
|
||||||
return renderTemplate(RESOURCE::templates::search_result_xml, mapper, library);
|
return renderTemplate(RESOURCE::templates::search_result_xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+3
-23
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
|
|
||||||
Server::Server(LibraryPtr library, std::shared_ptr<NameMapper> nameMapper) :
|
Server::Server(Library* library, NameMapper* nameMapper) :
|
||||||
mp_library(library),
|
mp_library(library),
|
||||||
mp_nameMapper(nameMapper),
|
mp_nameMapper(nameMapper),
|
||||||
mp_server(nullptr)
|
mp_server(nullptr)
|
||||||
@@ -51,7 +51,6 @@ bool Server::start() {
|
|||||||
m_withTaskbar,
|
m_withTaskbar,
|
||||||
m_withLibraryButton,
|
m_withLibraryButton,
|
||||||
m_blockExternalLinks,
|
m_blockExternalLinks,
|
||||||
m_ipMode,
|
|
||||||
m_indexTemplateString,
|
m_indexTemplateString,
|
||||||
m_ipConnectionLimit));
|
m_ipConnectionLimit));
|
||||||
return mp_server->start();
|
return mp_server->start();
|
||||||
@@ -75,33 +74,14 @@ void Server::setRoot(const std::string& root)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::setAddress(const std::string& addr)
|
int Server::getPort()
|
||||||
{
|
|
||||||
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();
|
return mp_server->getPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
IpAddress Server::getAddress() const
|
std::string Server::getAddress()
|
||||||
{
|
{
|
||||||
return mp_server->getAddress();
|
return mp_server->getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
IpMode Server::getIpMode() const
|
|
||||||
{
|
|
||||||
return mp_server->getIpMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-15
@@ -17,7 +17,7 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "i18n_utils.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
#include "tools/otherTools.h"
|
#include "tools/otherTools.h"
|
||||||
|
|
||||||
@@ -112,12 +112,8 @@ std::string expandParameterizedString(const std::string& lang,
|
|||||||
const std::string& key,
|
const std::string& key,
|
||||||
const Parameters& params)
|
const Parameters& params)
|
||||||
{
|
{
|
||||||
kainjow::mustache::object mustacheParams;
|
|
||||||
for( const auto& kv : params ) {
|
|
||||||
mustacheParams[kv.first] = kv.second;
|
|
||||||
}
|
|
||||||
const std::string tmpl = getTranslatedString(lang, key);
|
const std::string tmpl = getTranslatedString(lang, key);
|
||||||
return render_template(tmpl, mustacheParams);
|
return render_template(tmpl, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace i18n
|
} // namespace i18n
|
||||||
@@ -193,13 +189,4 @@ std::string selectMostSuitableLanguage(const UserLangPreferences& prefs)
|
|||||||
return bestLangSoFar;
|
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
|
} // namespace kiwix
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
* MA 02110-1301, USA.
|
* MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef KIWIX_SERVER_I18N_UTILS
|
#ifndef KIWIX_SERVER_I18N
|
||||||
#define KIWIX_SERVER_I18N_UTILS
|
#define KIWIX_SERVER_I18N
|
||||||
|
|
||||||
#include "i18n.h"
|
#include <string>
|
||||||
#include <mustache.hpp>
|
#include <mustache.hpp>
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix
|
||||||
@@ -39,9 +39,36 @@ struct I18nStringTable {
|
|||||||
const char* get(const std::string& key) const;
|
const char* get(const std::string& key) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
||||||
|
|
||||||
namespace i18n
|
namespace i18n
|
||||||
{
|
{
|
||||||
|
|
||||||
|
typedef kainjow::mustache::object Parameters;
|
||||||
|
|
||||||
|
std::string expandParameterizedString(const std::string& lang,
|
||||||
|
const std::string& key,
|
||||||
|
const Parameters& params);
|
||||||
|
|
||||||
|
class GetTranslatedString
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit GetTranslatedString(const std::string& lang) : m_lang(lang) {}
|
||||||
|
|
||||||
|
std::string operator()(const std::string& key) const
|
||||||
|
{
|
||||||
|
return getTranslatedString(m_lang, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const std::string& key, const Parameters& params) const
|
||||||
|
{
|
||||||
|
return expandParameterizedString(m_lang, key, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string m_lang;
|
||||||
|
};
|
||||||
|
|
||||||
class GetTranslatedStringWithMsgId
|
class GetTranslatedStringWithMsgId
|
||||||
{
|
{
|
||||||
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
||||||
@@ -66,6 +93,24 @@ private:
|
|||||||
|
|
||||||
} // namespace i18n
|
} // namespace i18n
|
||||||
|
|
||||||
|
struct ParameterizedMessage
|
||||||
|
{
|
||||||
|
public: // types
|
||||||
|
typedef kainjow::mustache::object Parameters;
|
||||||
|
|
||||||
|
public: // functions
|
||||||
|
ParameterizedMessage(const std::string& msgId, const Parameters& params)
|
||||||
|
: msgId(msgId)
|
||||||
|
, params(params)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string getText(const std::string& lang) const;
|
||||||
|
|
||||||
|
private: // data
|
||||||
|
const std::string msgId;
|
||||||
|
const Parameters params;
|
||||||
|
};
|
||||||
|
|
||||||
struct LangPreference
|
struct LangPreference
|
||||||
{
|
{
|
||||||
const std::string lang;
|
const std::string lang;
|
||||||
@@ -80,4 +125,4 @@ std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
|
|||||||
|
|
||||||
} // namespace kiwix
|
} // namespace kiwix
|
||||||
|
|
||||||
#endif // KIWIX_SERVER_I18N_UTILS
|
#endif // KIWIX_SERVER_I18N
|
||||||
+97
-145
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "internalServer.h"
|
#include "internalServer.h"
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifdef __FreeBSD__
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ extern "C" {
|
|||||||
#include "search_renderer.h"
|
#include "search_renderer.h"
|
||||||
#include "opds_dumper.h"
|
#include "opds_dumper.h"
|
||||||
#include "html_dumper.h"
|
#include "html_dumper.h"
|
||||||
#include "i18n_utils.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
#include <zim/uuid.h>
|
#include <zim/uuid.h>
|
||||||
#include <zim/error.h>
|
#include <zim/error.h>
|
||||||
@@ -85,20 +85,6 @@ namespace kiwix {
|
|||||||
namespace
|
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)
|
inline std::string normalizeRootUrl(std::string rootUrl)
|
||||||
{
|
{
|
||||||
while ( !rootUrl.empty() && rootUrl.back() == '/' )
|
while ( !rootUrl.empty() && rootUrl.back() == '/' )
|
||||||
@@ -204,6 +190,12 @@ ParameterizedMessage tooManyBooksMsg(size_t nbBooks, size_t limit)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
|
||||||
|
{
|
||||||
|
const ParameterizedMessage::Parameters noParams;
|
||||||
|
return ParameterizedMessage(msgId, noParams);
|
||||||
|
}
|
||||||
|
|
||||||
struct Error : public std::runtime_error {
|
struct Error : public std::runtime_error {
|
||||||
explicit Error(const ParameterizedMessage& message)
|
explicit Error(const ParameterizedMessage& message)
|
||||||
: std::runtime_error("Error while handling request"),
|
: std::runtime_error("Error while handling request"),
|
||||||
@@ -262,11 +254,6 @@ get_matching_if_none_match_etag(const RequestContext& r, const std::string& etag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoDelete
|
|
||||||
{
|
|
||||||
template<class T> void operator()(T*) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
|
|
||||||
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
|
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
|
||||||
@@ -419,9 +406,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
InternalServer::InternalServer(LibraryPtr library,
|
InternalServer::InternalServer(Library* library,
|
||||||
std::shared_ptr<NameMapper> nameMapper,
|
NameMapper* nameMapper,
|
||||||
IpAddress addr,
|
std::string addr,
|
||||||
int port,
|
int port,
|
||||||
std::string root,
|
std::string root,
|
||||||
int nbThreads,
|
int nbThreads,
|
||||||
@@ -430,7 +417,6 @@ InternalServer::InternalServer(LibraryPtr library,
|
|||||||
bool withTaskbar,
|
bool withTaskbar,
|
||||||
bool withLibraryButton,
|
bool withLibraryButton,
|
||||||
bool blockExternalLinks,
|
bool blockExternalLinks,
|
||||||
IpMode ipMode,
|
|
||||||
std::string indexTemplateString,
|
std::string indexTemplateString,
|
||||||
int ipConnectionLimit) :
|
int ipConnectionLimit) :
|
||||||
m_addr(addr),
|
m_addr(addr),
|
||||||
@@ -443,12 +429,11 @@ InternalServer::InternalServer(LibraryPtr library,
|
|||||||
m_withTaskbar(withTaskbar),
|
m_withTaskbar(withTaskbar),
|
||||||
m_withLibraryButton(withLibraryButton),
|
m_withLibraryButton(withLibraryButton),
|
||||||
m_blockExternalLinks(blockExternalLinks),
|
m_blockExternalLinks(blockExternalLinks),
|
||||||
m_ipMode(ipMode),
|
|
||||||
m_indexTemplateString(indexTemplateString.empty() ? RESOURCE::templates::index_html : indexTemplateString),
|
m_indexTemplateString(indexTemplateString.empty() ? RESOURCE::templates::index_html : indexTemplateString),
|
||||||
m_ipConnectionLimit(ipConnectionLimit),
|
m_ipConnectionLimit(ipConnectionLimit),
|
||||||
mp_daemon(nullptr),
|
mp_daemon(nullptr),
|
||||||
mp_library(library),
|
mp_library(library),
|
||||||
mp_nameMapper(nameMapper ? nameMapper : std::shared_ptr<NameMapper>(&defaultNameMapper, NoDelete())),
|
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper),
|
||||||
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
|
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
|
||||||
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
|
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
|
||||||
m_customizedResources(new CustomizedResources)
|
m_customizedResources(new CustomizedResources)
|
||||||
@@ -467,62 +452,28 @@ bool InternalServer::start() {
|
|||||||
if (m_verbose.load())
|
if (m_verbose.load())
|
||||||
flags |= MHD_USE_DEBUG;
|
flags |= MHD_USE_DEBUG;
|
||||||
|
|
||||||
|
struct sockaddr_in sockAddr;
|
||||||
struct sockaddr_in sockAddr4={0};
|
memset(&sockAddr, 0, sizeof(sockAddr));
|
||||||
sockAddr4.sin_family = AF_INET;
|
sockAddr.sin_family = AF_INET;
|
||||||
sockAddr4.sin_port = htons(m_port);
|
sockAddr.sin_port = htons(m_port);
|
||||||
struct sockaddr_in6 sockAddr6={0};
|
if (m_addr.empty()) {
|
||||||
sockAddr6.sin6_family = AF_INET6;
|
if (0 != INADDR_ANY) {
|
||||||
sockAddr6.sin6_port = htons(m_port);
|
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
}
|
||||||
if (m_addr.addr.empty() && m_addr.addr6.empty()) { // No ip address provided
|
m_addr = kiwix::getBestPublicIp();
|
||||||
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 {
|
} else {
|
||||||
const std::string addr = !m_addr.addr.empty() ? m_addr.addr : m_addr.addr6;
|
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;
|
||||||
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;
|
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,
|
mp_daemon = MHD_start_daemon(flags,
|
||||||
m_port,
|
m_port,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
&staticHandlerCallback,
|
&staticHandlerCallback,
|
||||||
this,
|
this,
|
||||||
MHD_OPTION_SOCK_ADDR, sockaddr,
|
MHD_OPTION_SOCK_ADDR, &sockAddr,
|
||||||
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
|
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
|
||||||
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
|
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
|
||||||
MHD_OPTION_END);
|
MHD_OPTION_END);
|
||||||
@@ -563,19 +514,6 @@ static MHD_Result staticHandlerCallback(void* cls,
|
|||||||
cont_cls);
|
cont_cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
MHD_Result add_name_value_pair(void *nvp, enum MHD_ValueKind kind,
|
|
||||||
const char *key, const char *value)
|
|
||||||
{
|
|
||||||
auto& nameValuePairs = *reinterpret_cast<RequestContext::NameValuePairs*>(nvp);
|
|
||||||
nameValuePairs.push_back({key, value});
|
|
||||||
return MHD_YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // unnamed namespace
|
|
||||||
|
|
||||||
MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||||
const char* fullUrl,
|
const char* fullUrl,
|
||||||
const char* method,
|
const char* method,
|
||||||
@@ -592,10 +530,7 @@ MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto url = fullURL2LocalURL(fullUrl, m_rootPrefixOfDecodedURL);
|
const auto url = fullURL2LocalURL(fullUrl, m_rootPrefixOfDecodedURL);
|
||||||
RequestContext::NameValuePairs headers, queryArgs;
|
RequestContext request(connection, m_root, url, method, version);
|
||||||
MHD_get_connection_values(connection, MHD_HEADER_KIND, add_name_value_pair, &headers);
|
|
||||||
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, add_name_value_pair, &queryArgs);
|
|
||||||
RequestContext request(m_root, url, method, version, headers, queryArgs);
|
|
||||||
|
|
||||||
if (m_verbose.load() ) {
|
if (m_verbose.load() ) {
|
||||||
request.print_debug_info();
|
request.print_debug_info();
|
||||||
@@ -624,7 +559,7 @@ MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
|||||||
response->set_etag_body(getLibraryId());
|
response->set_etag_body(getLibraryId());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ret = response->send(request, m_verbose.load(), connection);
|
auto ret = response->send(request, connection);
|
||||||
auto end_time = std::chrono::steady_clock::now();
|
auto end_time = std::chrono::steady_clock::now();
|
||||||
auto time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time);
|
auto time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time);
|
||||||
if (m_verbose.load()) {
|
if (m_verbose.load()) {
|
||||||
@@ -653,19 +588,20 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! request.is_valid_url()) {
|
if (! request.is_valid_url()) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( request.get_url() == "" ) {
|
if ( request.get_url() == "" ) {
|
||||||
// Redirect /ROOT_LOCATION to /ROOT_LOCATION/ (note the added slash)
|
// Redirect /ROOT_LOCATION to /ROOT_LOCATION/ (note the added slash)
|
||||||
// so that relative URLs are resolved correctly
|
// so that relative URLs are resolved correctly
|
||||||
const std::string query = getSearchComponent(request);
|
const std::string query = getSearchComponent(request);
|
||||||
return Response::build_redirect(m_root + "/" + query);
|
return Response::build_redirect(*this, m_root + "/" + query);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ETag etag = get_matching_if_none_match_etag(request, getLibraryId());
|
const ETag etag = get_matching_if_none_match_etag(request, getLibraryId());
|
||||||
if ( etag )
|
if ( etag )
|
||||||
return Response::build_304(etag);
|
return Response::build_304(*this, etag);
|
||||||
|
|
||||||
const auto url = request.get_url();
|
const auto url = request.get_url();
|
||||||
if ( isLocallyCustomizedResource(url) )
|
if ( isLocallyCustomizedResource(url) )
|
||||||
@@ -706,15 +642,15 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
|||||||
|
|
||||||
const std::string contentUrl = m_root + "/content" + urlEncode(url);
|
const std::string contentUrl = m_root + "/content" + urlEncode(url);
|
||||||
const std::string query = getSearchComponent(request);
|
const std::string query = getSearchComponent(request);
|
||||||
return Response::build_redirect(contentUrl + query);
|
return Response::build_redirect(*this, contentUrl + query);
|
||||||
} catch (std::exception& e) {
|
} catch (std::exception& e) {
|
||||||
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
|
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
|
||||||
return HTTP500Response(request)
|
return HTTP500Response(*this, request)
|
||||||
+ ParameterizedMessage("non-translated-text", {{"MSG", e.what()}});
|
+ e.what();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
fprintf(stderr, "===== Unhandled unknown error\n");
|
fprintf(stderr, "===== Unhandled unknown error\n");
|
||||||
return HTTP500Response(request)
|
return HTTP500Response(*this, request)
|
||||||
+ nonParameterizedMessage("unknown-error");
|
+ "Unknown error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,7 +663,7 @@ MustacheData InternalServer::get_default_data() const
|
|||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::build_homepage(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::build_homepage(const RequestContext& request)
|
||||||
{
|
{
|
||||||
return ContentResponse::build(m_indexTemplateString, get_default_data(), "text/html; charset=utf-8");
|
return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -756,7 +692,8 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( startsWith(request.get_url(), "/suggest/") ) {
|
if ( startsWith(request.get_url(), "/suggest/") ) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bookName, bookId;
|
std::string bookName, bookId;
|
||||||
@@ -770,7 +707,7 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (archive == nullptr) {
|
if (archive == nullptr) {
|
||||||
return HTTP404Response(request)
|
return HTTP404Response(*this, request)
|
||||||
+ noSuchBookErrorMsg(bookName);
|
+ noSuchBookErrorMsg(bookName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,7 +742,7 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
|||||||
results.addFTSearchSuggestion(request.get_user_language(), queryString);
|
results.addFTSearchSuggestion(request.get_user_language(), queryString);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContentResponse::build(results.getJSON(), "application/json; charset=utf-8");
|
return ContentResponse::build(*this, results.getJSON(), "application/json; charset=utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestContext& request)
|
||||||
@@ -819,7 +756,7 @@ std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestCo
|
|||||||
{"enable_link_blocking", m_blockExternalLinks ? "true" : "false" },
|
{"enable_link_blocking", m_blockExternalLinks ? "true" : "false" },
|
||||||
{"enable_library_button", m_withLibraryButton ? "true" : "false" }
|
{"enable_library_button", m_withLibraryButton ? "true" : "false" }
|
||||||
};
|
};
|
||||||
return ContentResponse::build(RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
|
return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string InternalServer::getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const
|
std::string InternalServer::getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const
|
||||||
@@ -849,7 +786,7 @@ std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& req
|
|||||||
{
|
{
|
||||||
const auto url = request.get_url();
|
const auto url = request.get_url();
|
||||||
const auto urlParts = kiwix::split(url, "/", true, false);
|
const auto urlParts = kiwix::split(url, "/", true, false);
|
||||||
HTMLDumper htmlDumper(mp_library.get(), mp_nameMapper.get());
|
HTMLDumper htmlDumper(mp_library, mp_nameMapper);
|
||||||
htmlDumper.setRootLocation(m_root);
|
htmlDumper.setRootLocation(m_root);
|
||||||
htmlDumper.setLibraryId(getLibraryId());
|
htmlDumper.setLibraryId(getLibraryId());
|
||||||
auto userLang = request.get_user_language();
|
auto userLang = request.get_user_language();
|
||||||
@@ -874,13 +811,19 @@ std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& req
|
|||||||
const auto bookId = mp_nameMapper->getIdForName(urlParts[2]);
|
const auto bookId = mp_nameMapper->getIdForName(urlParts[2]);
|
||||||
content = getNoJSDownloadPageHTML(bookId, userLang);
|
content = getNoJSDownloadPageHTML(bookId, userLang);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContentResponse::build(content, "text/html; charset=utf-8");
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
|
content,
|
||||||
|
"text/html; charset=utf-8"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -918,12 +861,14 @@ std::unique_ptr<Response> InternalServer::handle_skin(const RequestContext& requ
|
|||||||
try {
|
try {
|
||||||
const auto accessType = staticResourceAccessType(request, resourceCacheId);
|
const auto accessType = staticResourceAccessType(request, resourceCacheId);
|
||||||
auto response = ContentResponse::build(
|
auto response = ContentResponse::build(
|
||||||
|
*this,
|
||||||
getResource(resourceName),
|
getResource(resourceName),
|
||||||
getMimeTypeForFile(resourceName));
|
getMimeTypeForFile(resourceName));
|
||||||
response->set_kind(accessType);
|
response->set_kind(accessType);
|
||||||
return std::move(response);
|
return std::move(response);
|
||||||
} catch (const ResourceNotFound& e) {
|
} catch (const ResourceNotFound& e) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,17 +881,20 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
|
|||||||
if ( startsWith(request.get_url(), "/search/") ) {
|
if ( startsWith(request.get_url(), "/search/") ) {
|
||||||
if (request.get_url() == "/search/searchdescription.xml") {
|
if (request.get_url() == "/search/searchdescription.xml") {
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
RESOURCE::ft_opensearchdescription_xml,
|
RESOURCE::ft_opensearchdescription_xml,
|
||||||
get_default_data(),
|
get_default_data(),
|
||||||
"application/opensearchdescription+xml");
|
"application/opensearchdescription+xml");
|
||||||
}
|
}
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return handle_search_request(request);
|
return handle_search_request(request);
|
||||||
} catch (const Error& e) {
|
} catch (const Error& e) {
|
||||||
return HTTP400Response(request)
|
return HTTP400Response(*this, request)
|
||||||
|
+ invalidUrlMsg
|
||||||
+ e.message();
|
+ e.message();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -987,12 +935,11 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
|
|||||||
} catch(std::runtime_error& e) {
|
} catch(std::runtime_error& e) {
|
||||||
// Searcher->search will throw a runtime error if there is no valid xapian database to do the search.
|
// 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)
|
// (in case of zim file not containing a index)
|
||||||
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css_tmpl);
|
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css);
|
||||||
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
|
HTTPErrorResponse response(*this, request, MHD_HTTP_NOT_FOUND,
|
||||||
"fulltext-search-unavailable",
|
"fulltext-search-unavailable",
|
||||||
"404-page-heading",
|
"404-page-heading",
|
||||||
cssUrl,
|
cssUrl);
|
||||||
/*includeKiwixResponseData=*/true);
|
|
||||||
response += nonParameterizedMessage("no-search-results");
|
response += nonParameterizedMessage("no-search-results");
|
||||||
// XXX: Now this has to be handled by the iframe-based viewer which
|
// XXX: Now this has to be handled by the iframe-based viewer which
|
||||||
// XXX: has to resolve if the book selection resulted in a single book.
|
// XXX: has to resolve if the book selection resulted in a single book.
|
||||||
@@ -1010,24 +957,17 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
|
|||||||
const auto pageLength = getSearchPageSize(request);
|
const auto pageLength = getSearchPageSize(request);
|
||||||
|
|
||||||
/* Get the results */
|
/* Get the results */
|
||||||
SearchRenderer renderer(search->getResults(start-1, pageLength), start,
|
SearchRenderer renderer(search->getResults(start-1, pageLength), mp_nameMapper, mp_library, start,
|
||||||
search->getEstimatedMatches());
|
search->getEstimatedMatches());
|
||||||
renderer.setSearchPattern(searchInfo.pattern);
|
renderer.setSearchPattern(searchInfo.pattern);
|
||||||
renderer.setSearchBookQuery(searchInfo.bookFilterQuery);
|
renderer.setSearchBookQuery(searchInfo.bookFilterQuery);
|
||||||
renderer.setProtocolPrefix(m_root + "/content/");
|
renderer.setProtocolPrefix(m_root + "/content/");
|
||||||
renderer.setSearchProtocolPrefix(m_root + "/search");
|
renderer.setSearchProtocolPrefix(m_root + "/search");
|
||||||
renderer.setPageLength(pageLength);
|
renderer.setPageLength(pageLength);
|
||||||
renderer.setUserLang(request.get_user_language());
|
|
||||||
if (request.get_requested_format() == "xml") {
|
if (request.get_requested_format() == "xml") {
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8");
|
||||||
renderer.getXml(*mp_nameMapper, mp_library.get()),
|
|
||||||
"application/rss+xml; charset=utf-8"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
auto response = ContentResponse::build(
|
auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8");
|
||||||
renderer.getHtml(*mp_nameMapper, mp_library.get()),
|
|
||||||
"text/html; charset=utf-8"
|
|
||||||
);
|
|
||||||
// XXX: Now this has to be handled by the iframe-based viewer which
|
// XXX: Now this has to be handled by the iframe-based viewer which
|
||||||
// XXX: has to resolve if the book selection resulted in a single book.
|
// XXX: has to resolve if the book selection resulted in a single book.
|
||||||
/*
|
/*
|
||||||
@@ -1047,7 +987,8 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( startsWith(request.get_url(), "/random/") ) {
|
if ( startsWith(request.get_url(), "/random/") ) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bookName;
|
std::string bookName;
|
||||||
@@ -1061,7 +1002,7 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (archive == nullptr) {
|
if (archive == nullptr) {
|
||||||
return HTTP404Response(request)
|
return HTTP404Response(*this, request)
|
||||||
+ noSuchBookErrorMsg(bookName);
|
+ noSuchBookErrorMsg(bookName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1069,7 +1010,7 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
|||||||
auto entry = archive->getRandomEntry();
|
auto entry = archive->getRandomEntry();
|
||||||
return build_redirect(bookName, getFinalItem(*archive, entry));
|
return build_redirect(bookName, getFinalItem(*archive, entry));
|
||||||
} catch(zim::EntryNotFound& e) {
|
} catch(zim::EntryNotFound& e) {
|
||||||
return HTTP404Response(request)
|
return HTTP404Response(*this, request)
|
||||||
+ nonParameterizedMessage("random-article-failure");
|
+ nonParameterizedMessage("random-article-failure");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1082,12 +1023,13 @@ std::unique_ptr<Response> InternalServer::handle_captured_external(const Request
|
|||||||
} catch (const std::out_of_range& e) {}
|
} catch (const std::out_of_range& e) {}
|
||||||
|
|
||||||
if (source.empty()) {
|
if (source.empty()) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto data = get_default_data();
|
auto data = get_default_data();
|
||||||
data.set("source", source);
|
data.set("source", source);
|
||||||
return ContentResponse::build(RESOURCE::templates::captured_external_html, data, "text/html; charset=utf-8");
|
return ContentResponse::build(*this, RESOURCE::templates::captured_external_html, data, "text/html; charset=utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_catch(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::handle_catch(const RequestContext& request)
|
||||||
@@ -1100,7 +1042,8 @@ std::unique_ptr<Response> InternalServer::handle_catch(const RequestContext& req
|
|||||||
return handle_captured_external(request);
|
return handle_captured_external(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
@@ -1145,6 +1088,7 @@ const std::string CONTENT_CSP_HEADER =
|
|||||||
|
|
||||||
"sandbox allow-scripts "
|
"sandbox allow-scripts "
|
||||||
"allow-same-origin "
|
"allow-same-origin "
|
||||||
|
"allow-top-navigation-by-user-activation "
|
||||||
"allow-modals "
|
"allow-modals "
|
||||||
"allow-popups "
|
"allow-popups "
|
||||||
"allow-forms "
|
"allow-forms "
|
||||||
@@ -1160,7 +1104,7 @@ InternalServer::build_redirect(const std::string& bookName, const zim::Item& ite
|
|||||||
{
|
{
|
||||||
const auto contentPath = "/content/" + bookName + "/" + item.getPath();
|
const auto contentPath = "/content/" + bookName + "/" + item.getPath();
|
||||||
const auto url = m_root + kiwix::urlEncode(contentPath);
|
const auto url = m_root + kiwix::urlEncode(contentPath);
|
||||||
return Response::build_redirect(url);
|
return Response::build_redirect(*this, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& request)
|
||||||
@@ -1184,14 +1128,15 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
|||||||
|
|
||||||
if (archive == nullptr) {
|
if (archive == nullptr) {
|
||||||
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern);
|
const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern);
|
||||||
return UrlNotFoundResponse(request)
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg
|
||||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string archiveUuid(archive->getUuid());
|
const std::string archiveUuid(archive->getUuid());
|
||||||
const ETag etag = get_matching_if_none_match_etag(request, archiveUuid);
|
const ETag etag = get_matching_if_none_match_etag(request, archiveUuid);
|
||||||
if ( etag )
|
if ( etag )
|
||||||
return Response::build_304(etag);
|
return Response::build_304(*this, etag);
|
||||||
|
|
||||||
auto urlStr = url.substr(prefixLength + bookName.size());
|
auto urlStr = url.substr(prefixLength + bookName.size());
|
||||||
if (urlStr[0] == '/') {
|
if (urlStr[0] == '/') {
|
||||||
@@ -1210,7 +1155,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
|||||||
// '-' namespaces, in which case that resource is returned instead.
|
// '-' namespaces, in which case that resource is returned instead.
|
||||||
return build_redirect(bookName, getFinalItem(*archive, entry));
|
return build_redirect(bookName, getFinalItem(*archive, entry));
|
||||||
}
|
}
|
||||||
auto response = ItemResponse::build(request, entry.getItem());
|
auto response = ItemResponse::build(*this, request, entry.getItem());
|
||||||
response->set_etag_body(archiveUuid);
|
response->set_etag_body(archiveUuid);
|
||||||
|
|
||||||
if ( !startsWith(entry.getItem().getMimetype(), "application/pdf") ) {
|
if ( !startsWith(entry.getItem().getMimetype(), "application/pdf") ) {
|
||||||
@@ -1231,7 +1176,8 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
|||||||
printf("Failed to find %s\n", urlStr.c_str());
|
printf("Failed to find %s\n", urlStr.c_str());
|
||||||
|
|
||||||
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern);
|
std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern);
|
||||||
return UrlNotFoundResponse(request)
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg
|
||||||
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
+ suggestSearchMsg(searchURL, kiwix::urlDecode(pattern));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1249,11 +1195,13 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
|||||||
bookName = request.get_url_part(1);
|
bookName = request.get_url_part(1);
|
||||||
kind = request.get_url_part(2);
|
kind = request.get_url_part(2);
|
||||||
} catch (const std::out_of_range& e) {
|
} catch (const std::out_of_range& e) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind != "meta" && kind!= "content") {
|
if (kind != "meta" && kind!= "content") {
|
||||||
return UrlNotFoundResponse(request)
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg
|
||||||
+ invalidRawAccessMsg(kind);
|
+ invalidRawAccessMsg(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1264,14 +1212,15 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
|||||||
} catch (const std::out_of_range& e) {}
|
} catch (const std::out_of_range& e) {}
|
||||||
|
|
||||||
if (archive == nullptr) {
|
if (archive == nullptr) {
|
||||||
return UrlNotFoundResponse(request)
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg
|
||||||
+ noSuchBookErrorMsg(bookName);
|
+ noSuchBookErrorMsg(bookName);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string archiveUuid(archive->getUuid());
|
const std::string archiveUuid(archive->getUuid());
|
||||||
const ETag etag = get_matching_if_none_match_etag(request, archiveUuid);
|
const ETag etag = get_matching_if_none_match_etag(request, archiveUuid);
|
||||||
if ( etag )
|
if ( etag )
|
||||||
return Response::build_304(etag);
|
return Response::build_304(*this, etag);
|
||||||
|
|
||||||
// Remove the beggining of the path:
|
// Remove the beggining of the path:
|
||||||
// /raw/<bookName>/<kind>/foo
|
// /raw/<bookName>/<kind>/foo
|
||||||
@@ -1282,7 +1231,7 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
|||||||
try {
|
try {
|
||||||
if (kind == "meta") {
|
if (kind == "meta") {
|
||||||
auto item = archive->getMetadataItem(itemPath);
|
auto item = archive->getMetadataItem(itemPath);
|
||||||
auto response = ItemResponse::build(request, item);
|
auto response = ItemResponse::build(*this, request, item);
|
||||||
response->set_etag_body(archiveUuid);
|
response->set_etag_body(archiveUuid);
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
@@ -1290,7 +1239,7 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
|||||||
if (entry.isRedirect()) {
|
if (entry.isRedirect()) {
|
||||||
return build_redirect(bookName, entry.getItem(true));
|
return build_redirect(bookName, entry.getItem(true));
|
||||||
}
|
}
|
||||||
auto response = ItemResponse::build(request, entry.getItem());
|
auto response = ItemResponse::build(*this, request, entry.getItem());
|
||||||
response->set_etag_body(archiveUuid);
|
response->set_etag_body(archiveUuid);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -1298,7 +1247,8 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
|||||||
if (m_verbose.load()) {
|
if (m_verbose.load()) {
|
||||||
printf("Failed to find %s\n", itemPath.c_str());
|
printf("Failed to find %s\n", itemPath.c_str());
|
||||||
}
|
}
|
||||||
return UrlNotFoundResponse(request)
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg
|
||||||
+ rawEntryNotFoundMsg(kind, itemPath);
|
+ rawEntryNotFoundMsg(kind, itemPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1323,10 +1273,12 @@ std::unique_ptr<Response> InternalServer::handle_locally_customized_resource(con
|
|||||||
|
|
||||||
auto byteRange = request.get_range().resolve(resourceData.size());
|
auto byteRange = request.get_range().resolve(resourceData.size());
|
||||||
if (byteRange.kind() != ByteRange::RESOLVED_FULL_CONTENT) {
|
if (byteRange.kind() != ByteRange::RESOLVED_FULL_CONTENT) {
|
||||||
return Response::build_416(resourceData.size());
|
return Response::build_416(*this, resourceData.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContentResponse::build(resourceData, crd.mimeType);
|
return ContentResponse::build(*this,
|
||||||
|
resourceData,
|
||||||
|
crd.mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+12
-12
@@ -27,7 +27,6 @@ extern "C" {
|
|||||||
|
|
||||||
#include "library.h"
|
#include "library.h"
|
||||||
#include "name_mapper.h"
|
#include "name_mapper.h"
|
||||||
#include "tools.h"
|
|
||||||
|
|
||||||
#include <zim/search.h>
|
#include <zim/search.h>
|
||||||
#include <zim/suggestion.h>
|
#include <zim/suggestion.h>
|
||||||
@@ -93,9 +92,9 @@ class OPDSDumper;
|
|||||||
|
|
||||||
class InternalServer {
|
class InternalServer {
|
||||||
public:
|
public:
|
||||||
InternalServer(LibraryPtr library,
|
InternalServer(Library* library,
|
||||||
std::shared_ptr<NameMapper> nameMapper,
|
NameMapper* nameMapper,
|
||||||
IpAddress addr,
|
std::string addr,
|
||||||
int port,
|
int port,
|
||||||
std::string root,
|
std::string root,
|
||||||
int nbThreads,
|
int nbThreads,
|
||||||
@@ -104,7 +103,6 @@ class InternalServer {
|
|||||||
bool withTaskbar,
|
bool withTaskbar,
|
||||||
bool withLibraryButton,
|
bool withLibraryButton,
|
||||||
bool blockExternalLinks,
|
bool blockExternalLinks,
|
||||||
IpMode ipMode,
|
|
||||||
std::string indexTemplateString,
|
std::string indexTemplateString,
|
||||||
int ipConnectionLimit);
|
int ipConnectionLimit);
|
||||||
virtual ~InternalServer();
|
virtual ~InternalServer();
|
||||||
@@ -118,9 +116,8 @@ class InternalServer {
|
|||||||
void** cont_cls);
|
void** cont_cls);
|
||||||
bool start();
|
bool start();
|
||||||
void stop();
|
void stop();
|
||||||
IpAddress getAddress() const { return m_addr; }
|
std::string getAddress() { return m_addr; }
|
||||||
int getPort() const { return m_port; }
|
int getPort() { return m_port; }
|
||||||
IpMode getIpMode() const { return m_ipMode; }
|
|
||||||
|
|
||||||
private: // functions
|
private: // functions
|
||||||
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
||||||
@@ -167,7 +164,7 @@ class InternalServer {
|
|||||||
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
|
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
|
||||||
|
|
||||||
private: // data
|
private: // data
|
||||||
IpAddress m_addr;
|
std::string m_addr;
|
||||||
int m_port;
|
int m_port;
|
||||||
std::string m_root; // URI-encoded
|
std::string m_root; // URI-encoded
|
||||||
std::string m_rootPrefixOfDecodedURL; // URI-decoded
|
std::string m_rootPrefixOfDecodedURL; // URI-decoded
|
||||||
@@ -177,13 +174,12 @@ class InternalServer {
|
|||||||
bool m_withTaskbar;
|
bool m_withTaskbar;
|
||||||
bool m_withLibraryButton;
|
bool m_withLibraryButton;
|
||||||
bool m_blockExternalLinks;
|
bool m_blockExternalLinks;
|
||||||
IpMode m_ipMode;
|
|
||||||
std::string m_indexTemplateString;
|
std::string m_indexTemplateString;
|
||||||
int m_ipConnectionLimit;
|
int m_ipConnectionLimit;
|
||||||
struct MHD_Daemon* mp_daemon;
|
struct MHD_Daemon* mp_daemon;
|
||||||
|
|
||||||
LibraryPtr mp_library;
|
Library* mp_library;
|
||||||
std::shared_ptr<NameMapper> mp_nameMapper;
|
NameMapper* mp_nameMapper;
|
||||||
|
|
||||||
SearchCache searchCache;
|
SearchCache searchCache;
|
||||||
SuggestionSearcherCache suggestionSearcherCache;
|
SuggestionSearcherCache suggestionSearcherCache;
|
||||||
@@ -192,6 +188,10 @@ class InternalServer {
|
|||||||
|
|
||||||
class CustomizedResources;
|
class CustomizedResources;
|
||||||
std::unique_ptr<CustomizedResources> m_customizedResources;
|
std::unique_ptr<CustomizedResources> m_customizedResources;
|
||||||
|
|
||||||
|
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
||||||
|
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype);
|
||||||
|
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
|||||||
host = request.get_header("Host");
|
host = request.get_header("Host");
|
||||||
url = request.get_url_part(1);
|
url = request.get_url_part(1);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url == "v2") {
|
if (url == "v2") {
|
||||||
@@ -71,16 +72,17 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
|
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url == "searchdescription.xml") {
|
if (url == "searchdescription.xml") {
|
||||||
auto response = ContentResponse::build(RESOURCE::opensearchdescription_xml, get_default_data(), "application/opensearchdescription+xml");
|
auto response = ContentResponse::build(*this, RESOURCE::opensearchdescription_xml, get_default_data(), "application/opensearchdescription+xml");
|
||||||
return std::move(response);
|
return std::move(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
zim::Uuid uuid;
|
zim::Uuid uuid;
|
||||||
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
kiwix::OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||||
opdsDumper.setRootLocation(m_root);
|
opdsDumper.setRootLocation(m_root);
|
||||||
opdsDumper.setLibraryId(getLibraryId());
|
opdsDumper.setLibraryId(getLibraryId());
|
||||||
std::vector<std::string> bookIdsToDump;
|
std::vector<std::string> bookIdsToDump;
|
||||||
@@ -93,6 +95,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto response = ContentResponse::build(
|
auto response = ContentResponse::build(
|
||||||
|
*this,
|
||||||
opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()),
|
opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()),
|
||||||
opdsMimeType[OPDS_ACQUISITION_FEED]);
|
opdsMimeType[OPDS_ACQUISITION_FEED]);
|
||||||
return std::move(response);
|
return std::move(response);
|
||||||
@@ -108,14 +111,15 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
|
|||||||
try {
|
try {
|
||||||
url = request.get_url_part(2);
|
url = request.get_url_part(2);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url == "root.xml") {
|
if (url == "root.xml") {
|
||||||
return handle_catalog_v2_root(request);
|
return handle_catalog_v2_root(request);
|
||||||
} else if (url == "searchdescription.xml") {
|
} else if (url == "searchdescription.xml") {
|
||||||
const std::string endpoint_root = m_root + "/catalog/v2";
|
const std::string endpoint_root = m_root + "/catalog/v2";
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(*this,
|
||||||
RESOURCE::catalog_v2_searchdescription_xml,
|
RESOURCE::catalog_v2_searchdescription_xml,
|
||||||
kainjow::mustache::object({{"endpoint_root", endpoint_root}}),
|
kainjow::mustache::object({{"endpoint_root", endpoint_root}}),
|
||||||
"application/opensearchdescription+xml"
|
"application/opensearchdescription+xml"
|
||||||
@@ -134,7 +138,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
|
|||||||
} else if (url == "illustration") {
|
} else if (url == "illustration") {
|
||||||
return handle_catalog_v2_illustration(request);
|
return handle_catalog_v2_illustration(request);
|
||||||
} else {
|
} else {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +147,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
|
|||||||
{
|
{
|
||||||
const std::string libraryId = getLibraryId();
|
const std::string libraryId = getLibraryId();
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
RESOURCE::templates::catalog_v2_root_xml,
|
RESOURCE::templates::catalog_v2_root_xml,
|
||||||
kainjow::mustache::object{
|
kainjow::mustache::object{
|
||||||
{"date", gen_date_str()},
|
{"date", gen_date_str()},
|
||||||
@@ -158,12 +164,13 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
|
|||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
|
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
|
||||||
{
|
{
|
||||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||||
opdsDumper.setRootLocation(m_root);
|
opdsDumper.setRootLocation(m_root);
|
||||||
opdsDumper.setLibraryId(getLibraryId());
|
opdsDumper.setLibraryId(getLibraryId());
|
||||||
const auto bookIds = search_catalog(request, opdsDumper);
|
const auto bookIds = search_catalog(request, opdsDumper);
|
||||||
const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial);
|
const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial);
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
opdsFeed,
|
opdsFeed,
|
||||||
opdsMimeType[OPDS_ACQUISITION_FEED]
|
opdsMimeType[OPDS_ACQUISITION_FEED]
|
||||||
);
|
);
|
||||||
@@ -174,14 +181,16 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
|||||||
try {
|
try {
|
||||||
mp_library->getBookById(entryId);
|
mp_library->getBookById(entryId);
|
||||||
} catch (const std::out_of_range&) {
|
} catch (const std::out_of_range&) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||||
opdsDumper.setRootLocation(m_root);
|
opdsDumper.setRootLocation(m_root);
|
||||||
opdsDumper.setLibraryId(getLibraryId());
|
opdsDumper.setLibraryId(getLibraryId());
|
||||||
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
|
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
opdsFeed,
|
opdsFeed,
|
||||||
opdsMimeType[OPDS_ENTRY]
|
opdsMimeType[OPDS_ENTRY]
|
||||||
);
|
);
|
||||||
@@ -189,10 +198,11 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
|||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
|
||||||
{
|
{
|
||||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||||
opdsDumper.setRootLocation(m_root);
|
opdsDumper.setRootLocation(m_root);
|
||||||
opdsDumper.setLibraryId(getLibraryId());
|
opdsDumper.setLibraryId(getLibraryId());
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
opdsDumper.categoriesOPDSFeed(),
|
opdsDumper.categoriesOPDSFeed(),
|
||||||
opdsMimeType[OPDS_NAVIGATION_FEED]
|
opdsMimeType[OPDS_NAVIGATION_FEED]
|
||||||
);
|
);
|
||||||
@@ -200,10 +210,11 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
|
|||||||
|
|
||||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
|
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
|
||||||
{
|
{
|
||||||
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
|
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||||
opdsDumper.setRootLocation(m_root);
|
opdsDumper.setRootLocation(m_root);
|
||||||
opdsDumper.setLibraryId(getLibraryId());
|
opdsDumper.setLibraryId(getLibraryId());
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
opdsDumper.languagesOPDSFeed(),
|
opdsDumper.languagesOPDSFeed(),
|
||||||
opdsMimeType[OPDS_NAVIGATION_FEED]
|
opdsMimeType[OPDS_NAVIGATION_FEED]
|
||||||
);
|
);
|
||||||
@@ -217,11 +228,13 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_illustration(const R
|
|||||||
auto size = request.get_argument<unsigned int>("size");
|
auto size = request.get_argument<unsigned int>("size");
|
||||||
auto illustration = book.getIllustration(size);
|
auto illustration = book.getIllustration(size);
|
||||||
return ContentResponse::build(
|
return ContentResponse::build(
|
||||||
|
*this,
|
||||||
illustration->getData(),
|
illustration->getData(),
|
||||||
illustration->mimeType
|
illustration->mimeType
|
||||||
);
|
);
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
return UrlNotFoundResponse(request);
|
return HTTP404Response(*this, request)
|
||||||
|
+ urlNotFoundMsg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
#include "tools/stringTools.h"
|
#include "tools/stringTools.h"
|
||||||
#include "i18n_utils.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
|
|
||||||
@@ -51,12 +51,11 @@ RequestMethod str2RequestMethod(const std::string& method) {
|
|||||||
|
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
|
|
||||||
RequestContext::RequestContext(const std::string& _rootLocation, // URI-encoded
|
RequestContext::RequestContext(struct MHD_Connection* connection,
|
||||||
|
const std::string& _rootLocation, // URI-encoded
|
||||||
const std::string& unrootedUrl, // URI-decoded
|
const std::string& unrootedUrl, // URI-decoded
|
||||||
const std::string& _method,
|
const std::string& _method,
|
||||||
const std::string& version,
|
const std::string& version) :
|
||||||
const NameValuePairs& headers,
|
|
||||||
const NameValuePairs& queryArgs) :
|
|
||||||
rootLocation(_rootLocation),
|
rootLocation(_rootLocation),
|
||||||
url(unrootedUrl),
|
url(unrootedUrl),
|
||||||
method(str2RequestMethod(_method)),
|
method(str2RequestMethod(_method)),
|
||||||
@@ -65,13 +64,9 @@ RequestContext::RequestContext(const std::string& _rootLocation, // URI-encoded
|
|||||||
acceptEncodingGzip(false),
|
acceptEncodingGzip(false),
|
||||||
byteRange_()
|
byteRange_()
|
||||||
{
|
{
|
||||||
for ( const auto& kv : headers ) {
|
MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this);
|
||||||
add_header(kv.first, kv.second);
|
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this);
|
||||||
}
|
MHD_get_connection_values(connection, MHD_COOKIE_KIND, &RequestContext::fill_cookie, this);
|
||||||
|
|
||||||
for ( const auto& kv : queryArgs ) {
|
|
||||||
add_argument(kv.first, kv.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
acceptEncodingGzip =
|
acceptEncodingGzip =
|
||||||
@@ -88,14 +83,18 @@ RequestContext::RequestContext(const std::string& _rootLocation, // URI-encoded
|
|||||||
RequestContext::~RequestContext()
|
RequestContext::~RequestContext()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void RequestContext::add_header(const char *key, const char *value)
|
MHD_Result RequestContext::fill_header(void *__this, enum MHD_ValueKind kind,
|
||||||
|
const char *key, const char *value)
|
||||||
{
|
{
|
||||||
this->headers[lcAll(key)] = value;
|
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||||
|
_this->headers[lcAll(key)] = value;
|
||||||
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestContext::add_argument(const char *key, const char* value)
|
MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
||||||
|
const char *key, const char* value)
|
||||||
{
|
{
|
||||||
RequestContext *_this = this;
|
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||||
_this->arguments[key].push_back(value == nullptr ? "" : value);
|
_this->arguments[key].push_back(value == nullptr ? "" : value);
|
||||||
if ( ! _this->queryString.empty() ) {
|
if ( ! _this->queryString.empty() ) {
|
||||||
_this->queryString += "&";
|
_this->queryString += "&";
|
||||||
@@ -105,6 +104,15 @@ void RequestContext::add_argument(const char *key, const char* value)
|
|||||||
_this->queryString += "=";
|
_this->queryString += "=";
|
||||||
_this->queryString += urlEncode(value);
|
_this->queryString += urlEncode(value);
|
||||||
}
|
}
|
||||||
|
return MHD_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
MHD_Result RequestContext::fill_cookie(void *__this, enum MHD_ValueKind kind,
|
||||||
|
const char *key, const char* value)
|
||||||
|
{
|
||||||
|
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||||
|
_this->cookies[key] = value == nullptr ? "" : value;
|
||||||
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequestContext::print_debug_info() const {
|
void RequestContext::print_debug_info() const {
|
||||||
@@ -194,12 +202,21 @@ std::string RequestContext::get_user_language() const
|
|||||||
return userlang.lang;
|
return userlang.lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RequestContext::user_language_comes_from_cookie() const
|
||||||
|
{
|
||||||
|
return userlang.selectedBy == UserLanguage::SelectorKind::COOKIE;
|
||||||
|
}
|
||||||
|
|
||||||
RequestContext::UserLanguage RequestContext::determine_user_language() const
|
RequestContext::UserLanguage RequestContext::determine_user_language() const
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return {UserLanguage::SelectorKind::QUERY_PARAM, get_argument("userlang")};
|
return {UserLanguage::SelectorKind::QUERY_PARAM, get_argument("userlang")};
|
||||||
} catch(const std::out_of_range&) {}
|
} catch(const std::out_of_range&) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {UserLanguage::SelectorKind::COOKIE, cookies.at("userlang")};
|
||||||
|
} catch(const std::out_of_range&) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const std::string acceptLanguage = get_header("Accept-Language");
|
const std::string acceptLanguage = get_header("Accept-Language");
|
||||||
const auto userLangPrefs = parseUserLanguagePreferences(acceptLanguage);
|
const auto userLangPrefs = parseUserLanguagePreferences(acceptLanguage);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "byte_range.h"
|
#include "byte_range.h"
|
||||||
#include "../tools/stringTools.h"
|
#include "tools/stringTools.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "microhttpd_wrapper.h"
|
#include "microhttpd_wrapper.h"
|
||||||
@@ -55,17 +55,12 @@ class IndexError: public std::runtime_error {};
|
|||||||
|
|
||||||
|
|
||||||
class RequestContext {
|
class RequestContext {
|
||||||
public: // types
|
|
||||||
typedef std::vector<std::pair<const char*, const char*>> NameValuePairs;
|
|
||||||
|
|
||||||
public: // functions
|
public: // functions
|
||||||
RequestContext(const std::string& rootLocation, // URI-encoded
|
RequestContext(struct MHD_Connection* connection,
|
||||||
|
const std::string& rootLocation, // URI-encoded
|
||||||
const std::string& unrootedUrl, // URI-decoded
|
const std::string& unrootedUrl, // URI-decoded
|
||||||
const std::string& method,
|
const std::string& method,
|
||||||
const std::string& version,
|
const std::string& version);
|
||||||
const NameValuePairs& headers,
|
|
||||||
const NameValuePairs& queryArgs);
|
|
||||||
|
|
||||||
~RequestContext();
|
~RequestContext();
|
||||||
|
|
||||||
void print_debug_info() const;
|
void print_debug_info() const;
|
||||||
@@ -124,12 +119,15 @@ class RequestContext {
|
|||||||
std::string get_user_language() const;
|
std::string get_user_language() const;
|
||||||
std::string get_requested_format() const;
|
std::string get_requested_format() const;
|
||||||
|
|
||||||
|
bool user_language_comes_from_cookie() const;
|
||||||
|
|
||||||
private: // types
|
private: // types
|
||||||
struct UserLanguage
|
struct UserLanguage
|
||||||
{
|
{
|
||||||
enum SelectorKind
|
enum SelectorKind
|
||||||
{
|
{
|
||||||
QUERY_PARAM,
|
QUERY_PARAM,
|
||||||
|
COOKIE,
|
||||||
ACCEPT_LANGUAGE_HEADER,
|
ACCEPT_LANGUAGE_HEADER,
|
||||||
DEFAULT
|
DEFAULT
|
||||||
};
|
};
|
||||||
@@ -150,14 +148,16 @@ class RequestContext {
|
|||||||
ByteRange byteRange_;
|
ByteRange byteRange_;
|
||||||
std::map<std::string, std::string> headers;
|
std::map<std::string, std::string> headers;
|
||||||
std::map<std::string, std::vector<std::string>> arguments;
|
std::map<std::string, std::vector<std::string>> arguments;
|
||||||
|
std::map<std::string, std::string> cookies;
|
||||||
std::string queryString;
|
std::string queryString;
|
||||||
UserLanguage userlang;
|
UserLanguage userlang;
|
||||||
|
|
||||||
private: // functions
|
private: // functions
|
||||||
UserLanguage determine_user_language() const;
|
UserLanguage determine_user_language() const;
|
||||||
|
|
||||||
void add_header(const char* name, const char* value);
|
static MHD_Result fill_header(void *, enum MHD_ValueKind, const char*, const char*);
|
||||||
void add_argument(const char* name, const char* value);
|
static MHD_Result fill_cookie(void *, enum MHD_ValueKind, const char*, const char*);
|
||||||
|
static MHD_Result fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<> std::string RequestContext::get_argument(const std::string& name) const;
|
template<> std::string RequestContext::get_argument(const std::string& name) const;
|
||||||
|
|||||||
+98
-263
@@ -32,9 +32,6 @@
|
|||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <list>
|
|
||||||
#include <map>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
// This is somehow a magic value.
|
// This is somehow a magic value.
|
||||||
// If this value is too small, we will compress (and lost cpu time) too much
|
// If this value is too small, we will compress (and lost cpu time) too much
|
||||||
@@ -50,8 +47,6 @@ namespace kiwix {
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
typedef kainjow::mustache::data MustacheData;
|
|
||||||
|
|
||||||
// some utilities
|
// some utilities
|
||||||
|
|
||||||
std::string get_mime_type(const zim::Item& item)
|
std::string get_mime_type(const zim::Item& item)
|
||||||
@@ -124,8 +119,9 @@ const char* getCacheControlHeader(Response::Kind k)
|
|||||||
|
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
|
|
||||||
Response::Response()
|
Response::Response(bool verbose)
|
||||||
: m_returnCode(MHD_HTTP_OK)
|
: m_verbose(verbose),
|
||||||
|
m_returnCode(MHD_HTTP_OK)
|
||||||
{
|
{
|
||||||
add_header(MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
add_header(MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||||
}
|
}
|
||||||
@@ -137,14 +133,14 @@ void Response::set_kind(Kind k)
|
|||||||
m_etag.set_option(ETag::ZIM_CONTENT);
|
m_etag.set_option(ETag::ZIM_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> Response::build()
|
std::unique_ptr<Response> Response::build(const InternalServer& server)
|
||||||
{
|
{
|
||||||
return std::make_unique<Response>();
|
return std::unique_ptr<Response>(new Response(server.m_verbose.load()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> Response::build_304(const ETag& etag)
|
std::unique_ptr<Response> Response::build_304(const InternalServer& server, const ETag& etag)
|
||||||
{
|
{
|
||||||
auto response = Response::build();
|
auto response = Response::build(server);
|
||||||
response->set_code(MHD_HTTP_NOT_MODIFIED);
|
response->set_code(MHD_HTTP_NOT_MODIFIED);
|
||||||
response->m_etag = etag;
|
response->m_etag = etag;
|
||||||
if ( etag.get_option(ETag::ZIM_CONTENT) ) {
|
if ( etag.get_option(ETag::ZIM_CONTENT) ) {
|
||||||
@@ -156,260 +152,67 @@ std::unique_ptr<Response> Response::build_304(const ETag& etag)
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UrlNotFoundMsg urlNotFoundMsg;
|
||||||
|
const InvalidUrlMsg invalidUrlMsg;
|
||||||
|
|
||||||
namespace
|
std::string ContentResponseBlueprint::getMessage(const std::string& msgId) const
|
||||||
{
|
{
|
||||||
|
return getTranslatedString(m_request.get_user_language(), msgId);
|
||||||
// This class was introduced in order to work around the missing support
|
|
||||||
// for std::variant (and std::optional) under some of the current build
|
|
||||||
// platforms.
|
|
||||||
template<class T>
|
|
||||||
class Optional
|
|
||||||
{
|
|
||||||
public: // functions
|
|
||||||
Optional() {}
|
|
||||||
Optional(const T& t) : ptr(new T(t)) {}
|
|
||||||
Optional(const Optional& o) : ptr(o.has_value() ? new T(*o) : nullptr) {}
|
|
||||||
Optional(Optional&& o) : ptr(std::move(o.ptr)) {}
|
|
||||||
|
|
||||||
Optional& operator=(const Optional& o)
|
|
||||||
{
|
|
||||||
*this = Optional(o);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional& operator=(Optional&& o)
|
|
||||||
{
|
|
||||||
ptr = std::move(o.ptr);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_value() const { return ptr.get() != nullptr; }
|
|
||||||
const T& operator*() const { return *ptr; }
|
|
||||||
T& operator*() { return *ptr; }
|
|
||||||
|
|
||||||
private: // data
|
|
||||||
std::unique_ptr<T> ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // unnamed namespace
|
|
||||||
|
|
||||||
class ContentResponseBlueprint::Data
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef std::list<Data> List;
|
|
||||||
typedef std::map<std::string, Data> Object;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// std::variant<std::string, bool, List, Object> data;
|
|
||||||
// XXX: libkiwix is compiled on platforms where std::variant
|
|
||||||
// XXX: is not yet supported. Hence this hack. Only one
|
|
||||||
// XXX: of the below data members is expected to contain a value.
|
|
||||||
Optional<std::string> m_stringValue;
|
|
||||||
Optional<bool> m_boolValue;
|
|
||||||
Optional<List> m_listValue;
|
|
||||||
Optional<Object> m_objectValue;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Data() {}
|
|
||||||
Data(const std::string& s) : m_stringValue(s) {}
|
|
||||||
Data(bool b) : m_boolValue(b) {}
|
|
||||||
Data(const List& l) : m_listValue(l) {}
|
|
||||||
Data(const Object& o) : m_objectValue(o) {}
|
|
||||||
|
|
||||||
MustacheData toMustache(const std::string& lang) const;
|
|
||||||
|
|
||||||
Data& operator[](const std::string& key)
|
|
||||||
{
|
|
||||||
return (*m_objectValue)[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
void push_back(const Data& d) { (*m_listValue).push_back(d); }
|
|
||||||
|
|
||||||
static Data onlyAsNonEmptyValue(const std::string& s)
|
|
||||||
{
|
|
||||||
return s.empty() ? Data(false) : Data(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Data from(const ParameterizedMessage& pmsg)
|
|
||||||
{
|
|
||||||
Object obj;
|
|
||||||
for(const auto& kv : pmsg.getParams()) {
|
|
||||||
obj[kv.first] = kv.second;
|
|
||||||
}
|
|
||||||
return Object{
|
|
||||||
{ "msgid", pmsg.getMsgId() },
|
|
||||||
{ "params", Data(obj) }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string asJSON() const;
|
|
||||||
void dumpJSON(std::ostream& os) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool isString() const { return m_stringValue.has_value(); }
|
|
||||||
bool isList() const { return m_listValue.has_value(); }
|
|
||||||
bool isObject() const { return m_objectValue.has_value(); }
|
|
||||||
|
|
||||||
const std::string& stringValue() const { return *m_stringValue; }
|
|
||||||
bool boolValue() const { return *m_boolValue; }
|
|
||||||
const List& listValue() const { return *m_listValue; }
|
|
||||||
const Object& objectValue() const { return *m_objectValue; }
|
|
||||||
|
|
||||||
const Data* get(const std::string& key) const
|
|
||||||
{
|
|
||||||
if ( !isObject() )
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
const auto& obj = objectValue();
|
|
||||||
const auto it = obj.find(key);
|
|
||||||
return it != obj.end() ? &it->second : nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MustacheData ContentResponseBlueprint::Data::toMustache(const std::string& lang) const
|
|
||||||
{
|
|
||||||
if ( this->isList() ) {
|
|
||||||
kainjow::mustache::list l;
|
|
||||||
for ( const auto& x : this->listValue() ) {
|
|
||||||
l.push_back(x.toMustache(lang));
|
|
||||||
}
|
|
||||||
return l;
|
|
||||||
} else if ( this->isObject() ) {
|
|
||||||
const Data* msgId = this->get("msgid");
|
|
||||||
const Data* msgParams = this->get("params");
|
|
||||||
if ( msgId && msgId->isString() && msgParams && msgParams->isObject() ) {
|
|
||||||
std::map<std::string, std::string> params;
|
|
||||||
for(const auto& kv : msgParams->objectValue()) {
|
|
||||||
params[kv.first] = kv.second.stringValue();
|
|
||||||
}
|
|
||||||
const ParameterizedMessage msg(msgId->stringValue(), ParameterizedMessage::Parameters(params));
|
|
||||||
return msg.getText(lang);
|
|
||||||
} else {
|
|
||||||
kainjow::mustache::object o;
|
|
||||||
for ( const auto& kv : this->objectValue() ) {
|
|
||||||
o[kv.first] = kv.second.toMustache(lang);
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
} else if ( this->isString() ) {
|
|
||||||
return this->stringValue();
|
|
||||||
} else {
|
|
||||||
return this->boolValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentResponseBlueprint::Data::dumpJSON(std::ostream& os) const
|
|
||||||
{
|
|
||||||
if ( this->isString() ) {
|
|
||||||
os << '"' << escapeForJSON(this->stringValue()) << '"';
|
|
||||||
} else if ( this->isList() ) {
|
|
||||||
const char * sep = " ";
|
|
||||||
os << "[";
|
|
||||||
|
|
||||||
for ( const auto& x : this->listValue() ) {
|
|
||||||
os << sep;
|
|
||||||
x.dumpJSON(os);
|
|
||||||
sep = ", ";
|
|
||||||
}
|
|
||||||
os << " ]";
|
|
||||||
} else if ( this->isObject() ) {
|
|
||||||
const char * sep = " ";
|
|
||||||
os << "{";
|
|
||||||
for ( const auto& kv : this->objectValue() ) {
|
|
||||||
os << sep << '"' << kv.first << "\" : ";
|
|
||||||
kv.second.dumpJSON(os);
|
|
||||||
sep = ", ";
|
|
||||||
}
|
|
||||||
os << " }";
|
|
||||||
} else {
|
|
||||||
os << (this->boolValue() ? "true" : "false");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ContentResponseBlueprint::Data::asJSON() const
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
this->dumpJSON(oss);
|
|
||||||
|
|
||||||
// This JSON is going to be used in HTML inside a <script></script> tag.
|
|
||||||
// If it contains "</script>" (or "</script >") as a substring, then the HTML
|
|
||||||
// parser will be confused. Since for a valid JSON that may happen only inside
|
|
||||||
// a JSON string, we can safely take advantage of the answers to
|
|
||||||
// https://stackoverflow.com/questions/28259389/how-to-put-script-in-a-javascript-string
|
|
||||||
// and work around the issue by inserting an otherwise harmless backslash.
|
|
||||||
return std::regex_replace(oss.str(), std::regex("</script"), "</scr\\ipt");
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentResponseBlueprint::ContentResponseBlueprint(const RequestContext* request,
|
|
||||||
int httpStatusCode,
|
|
||||||
const std::string& mimeType,
|
|
||||||
const std::string& templateStr,
|
|
||||||
bool includeKiwixResponseData)
|
|
||||||
: m_request(*request)
|
|
||||||
, m_httpStatusCode(httpStatusCode)
|
|
||||||
, m_mimeType(mimeType)
|
|
||||||
, m_template(templateStr)
|
|
||||||
, m_includeKiwixResponseData(includeKiwixResponseData)
|
|
||||||
, m_data(new Data)
|
|
||||||
{}
|
|
||||||
|
|
||||||
ContentResponseBlueprint::~ContentResponseBlueprint() = default;
|
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObject() const
|
std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObject() const
|
||||||
{
|
{
|
||||||
kainjow::mustache::data d = m_data->toMustache(m_request.get_user_language());
|
auto r = ContentResponse::build(m_server, m_template, m_data, m_mimeType);
|
||||||
if ( m_includeKiwixResponseData ) {
|
|
||||||
d.set("KIWIX_RESPONSE_TEMPLATE", escapeForJSON(m_template, false));
|
|
||||||
d.set("KIWIX_RESPONSE_DATA", m_data->asJSON());
|
|
||||||
}
|
|
||||||
auto r = ContentResponse::build(m_template, d, m_mimeType);
|
|
||||||
r->set_code(m_httpStatusCode);
|
r->set_code(m_httpStatusCode);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTPErrorResponse::HTTPErrorResponse(const RequestContext& request,
|
HTTPErrorResponse::HTTPErrorResponse(const InternalServer& server,
|
||||||
|
const RequestContext& request,
|
||||||
int httpStatusCode,
|
int httpStatusCode,
|
||||||
const std::string& pageTitleMsgId,
|
const std::string& pageTitleMsgId,
|
||||||
const std::string& headingMsgId,
|
const std::string& headingMsgId,
|
||||||
const std::string& cssUrl,
|
const std::string& cssUrl)
|
||||||
bool includeKiwixResponseData)
|
: ContentResponseBlueprint(&server,
|
||||||
: ContentResponseBlueprint(&request,
|
&request,
|
||||||
httpStatusCode,
|
httpStatusCode,
|
||||||
request.get_requested_format() == "html" ? "text/html; charset=utf-8" : "application/xml; charset=utf-8",
|
request.get_requested_format() == "html" ? "text/html; charset=utf-8" : "application/xml; charset=utf-8",
|
||||||
request.get_requested_format() == "html" ? RESOURCE::templates::error_html : RESOURCE::templates::error_xml,
|
request.get_requested_format() == "html" ? RESOURCE::templates::error_html : RESOURCE::templates::error_xml)
|
||||||
includeKiwixResponseData)
|
|
||||||
{
|
{
|
||||||
Data::List emptyList;
|
kainjow::mustache::list emptyList;
|
||||||
*this->m_data = Data(Data::Object{
|
this->m_data = kainjow::mustache::object{
|
||||||
{"CSS_URL", Data::onlyAsNonEmptyValue(cssUrl) },
|
{"CSS_URL", onlyAsNonEmptyMustacheValue(cssUrl) },
|
||||||
{"PAGE_TITLE", Data::from(nonParameterizedMessage(pageTitleMsgId))},
|
{"PAGE_TITLE", getMessage(pageTitleMsgId)},
|
||||||
{"PAGE_HEADING", Data::from(nonParameterizedMessage(headingMsgId))},
|
{"PAGE_HEADING", getMessage(headingMsgId)},
|
||||||
{"details", emptyList}
|
{"details", emptyList}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTP404Response::HTTP404Response(const RequestContext& request)
|
HTTP404Response::HTTP404Response(const InternalServer& server,
|
||||||
: HTTPErrorResponse(request,
|
const RequestContext& request)
|
||||||
|
: HTTPErrorResponse(server,
|
||||||
|
request,
|
||||||
MHD_HTTP_NOT_FOUND,
|
MHD_HTTP_NOT_FOUND,
|
||||||
"404-page-title",
|
"404-page-title",
|
||||||
"404-page-heading",
|
"404-page-heading")
|
||||||
std::string(),
|
|
||||||
/*includeKiwixResponseData=*/true)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlNotFoundResponse::UrlNotFoundResponse(const RequestContext& request)
|
HTTPErrorResponse& HTTP404Response::operator+(UrlNotFoundMsg /*unused*/)
|
||||||
: HTTP404Response(request)
|
|
||||||
{
|
{
|
||||||
const std::string requestUrl = urlDecode(m_request.get_full_url(), false);
|
const std::string requestUrl = urlDecode(m_request.get_full_url(), false);
|
||||||
*this += ParameterizedMessage("url-not-found", {{"url", requestUrl}});
|
return *this + ParameterizedMessage("url-not-found", {{"url", requestUrl}});
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPErrorResponse& HTTPErrorResponse::operator+(const std::string& msg)
|
||||||
|
{
|
||||||
|
m_data["details"].push_back({"p", msg});
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTPErrorResponse& HTTPErrorResponse::operator+(const ParameterizedMessage& details)
|
HTTPErrorResponse& HTTPErrorResponse::operator+(const ParameterizedMessage& details)
|
||||||
{
|
{
|
||||||
(*m_data)["details"].push_back(Data::Object{{"p", Data::from(details)}});
|
return *this + details.getText(m_request.get_user_language());
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTPErrorResponse& HTTPErrorResponse::operator+=(const ParameterizedMessage& details)
|
HTTPErrorResponse& HTTPErrorResponse::operator+=(const ParameterizedMessage& details)
|
||||||
@@ -419,36 +222,50 @@ HTTPErrorResponse& HTTPErrorResponse::operator+=(const ParameterizedMessage& det
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HTTP400Response::HTTP400Response(const RequestContext& request)
|
HTTP400Response::HTTP400Response(const InternalServer& server,
|
||||||
: HTTPErrorResponse(request,
|
const RequestContext& request)
|
||||||
|
: HTTPErrorResponse(server,
|
||||||
|
request,
|
||||||
MHD_HTTP_BAD_REQUEST,
|
MHD_HTTP_BAD_REQUEST,
|
||||||
"400-page-title",
|
"400-page-title",
|
||||||
"400-page-heading",
|
"400-page-heading")
|
||||||
std::string(),
|
{
|
||||||
/*includeKiwixResponseData=*/true)
|
}
|
||||||
|
|
||||||
|
HTTPErrorResponse& HTTP400Response::operator+(InvalidUrlMsg /*unused*/)
|
||||||
{
|
{
|
||||||
std::string requestUrl = urlDecode(m_request.get_full_url(), false);
|
std::string requestUrl = urlDecode(m_request.get_full_url(), false);
|
||||||
const auto query = m_request.get_query();
|
const auto query = m_request.get_query();
|
||||||
if (!query.empty()) {
|
if (!query.empty()) {
|
||||||
requestUrl += "?" + encodeDiples(query);
|
requestUrl += "?" + encodeDiples(query);
|
||||||
}
|
}
|
||||||
*this += ParameterizedMessage("invalid-request", {{"url", requestUrl}});
|
kainjow::mustache::mustache msgTmpl(R"(The requested URL "{{{url}}}" is not a valid request.)");
|
||||||
|
return *this + msgTmpl.render({"url", requestUrl});
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTP500Response::HTTP500Response(const RequestContext& request)
|
HTTP500Response::HTTP500Response(const InternalServer& server,
|
||||||
: HTTPErrorResponse(request,
|
const RequestContext& request)
|
||||||
|
: HTTPErrorResponse(server,
|
||||||
|
request,
|
||||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||||
"500-page-title",
|
"500-page-title",
|
||||||
"500-page-heading",
|
"500-page-heading")
|
||||||
std::string(),
|
|
||||||
/*includeKiwixResponseData=*/true)
|
|
||||||
{
|
{
|
||||||
*this += nonParameterizedMessage("500-page-text");
|
// operator+() is a state-modifying operator (akin to operator+=)
|
||||||
|
*this + "An internal server error occured. We are sorry about that :/";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> Response::build_416(size_t resourceLength)
|
std::unique_ptr<ContentResponse> HTTP500Response::generateResponseObject() const
|
||||||
{
|
{
|
||||||
auto response = Response::build();
|
const std::string mimeType = "text/html;charset=utf-8";
|
||||||
|
auto r = ContentResponse::build(m_server, m_template, m_data, mimeType);
|
||||||
|
r->set_code(m_httpStatusCode);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Response> Response::build_416(const InternalServer& server, size_t resourceLength)
|
||||||
|
{
|
||||||
|
auto response = Response::build(server);
|
||||||
// [FIXME] (compile with recent enough version of libmicrohttpd)
|
// [FIXME] (compile with recent enough version of libmicrohttpd)
|
||||||
// response->set_code(MHD_HTTP_RANGE_NOT_SATISFIABLE);
|
// response->set_code(MHD_HTTP_RANGE_NOT_SATISFIABLE);
|
||||||
response->set_code(416);
|
response->set_code(416);
|
||||||
@@ -460,9 +277,9 @@ std::unique_ptr<Response> Response::build_416(size_t resourceLength)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<Response> Response::build_redirect(const std::string& redirectUrl)
|
std::unique_ptr<Response> Response::build_redirect(const InternalServer& server, const std::string& redirectUrl)
|
||||||
{
|
{
|
||||||
auto response = Response::build();
|
auto response = Response::build(server);
|
||||||
response->m_returnCode = MHD_HTTP_FOUND;
|
response->m_returnCode = MHD_HTTP_FOUND;
|
||||||
response->add_header(MHD_HTTP_HEADER_LOCATION, redirectUrl);
|
response->add_header(MHD_HTTP_HEADER_LOCATION, redirectUrl);
|
||||||
return response;
|
return response;
|
||||||
@@ -557,7 +374,7 @@ ContentResponse::create_mhd_response(const RequestContext& request)
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
MHD_Result Response::send(const RequestContext& request, bool verbose, MHD_Connection* connection)
|
MHD_Result Response::send(const RequestContext& request, MHD_Connection* connection)
|
||||||
{
|
{
|
||||||
MHD_Response* response = create_mhd_response(request);
|
MHD_Response* response = create_mhd_response(request);
|
||||||
|
|
||||||
@@ -570,10 +387,17 @@ MHD_Result Response::send(const RequestContext& request, bool verbose, MHD_Conne
|
|||||||
MHD_add_response_header(response, p.first.c_str(), p.second.c_str());
|
MHD_add_response_header(response, p.first.c_str(), p.second.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! request.user_language_comes_from_cookie() ) {
|
||||||
|
const std::string cookie = "userlang=" + request.get_user_language()
|
||||||
|
+ ";Path=" + request.get_root_path()
|
||||||
|
+ ";Max-Age=31536000";
|
||||||
|
MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cookie.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
|
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
|
||||||
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;
|
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;
|
||||||
|
|
||||||
if (verbose)
|
if (m_verbose)
|
||||||
print_response_info(m_returnCode, response);
|
print_response_info(m_returnCode, response);
|
||||||
|
|
||||||
auto ret = MHD_queue_response(connection, m_returnCode, response);
|
auto ret = MHD_queue_response(connection, m_returnCode, response);
|
||||||
@@ -581,8 +405,9 @@ MHD_Result Response::send(const RequestContext& request, bool verbose, MHD_Conne
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentResponse::ContentResponse(const std::string& content, const std::string& mimetype) :
|
ContentResponse::ContentResponse(const std::string& root, bool verbose, const std::string& content, const std::string& mimetype) :
|
||||||
Response(),
|
Response(verbose),
|
||||||
|
m_root(root),
|
||||||
m_content(content),
|
m_content(content),
|
||||||
m_mimeType(mimetype)
|
m_mimeType(mimetype)
|
||||||
{
|
{
|
||||||
@@ -590,23 +415,29 @@ ContentResponse::ContentResponse(const std::string& content, const std::string&
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> ContentResponse::build(
|
std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||||
|
const InternalServer& server,
|
||||||
const std::string& content,
|
const std::string& content,
|
||||||
const std::string& mimetype)
|
const std::string& mimetype)
|
||||||
{
|
{
|
||||||
return std::make_unique<ContentResponse>(content, mimetype);
|
return std::unique_ptr<ContentResponse>(new ContentResponse(
|
||||||
|
server.m_root,
|
||||||
|
server.m_verbose.load(),
|
||||||
|
content,
|
||||||
|
mimetype));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> ContentResponse::build(
|
std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||||
|
const InternalServer& server,
|
||||||
const std::string& template_str,
|
const std::string& template_str,
|
||||||
kainjow::mustache::data data,
|
kainjow::mustache::data data,
|
||||||
const std::string& mimetype)
|
const std::string& mimetype)
|
||||||
{
|
{
|
||||||
auto content = render_template(template_str, data);
|
auto content = render_template(template_str, data);
|
||||||
return ContentResponse::build(content, mimetype);
|
return ContentResponse::build(server, content, mimetype);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemResponse::ItemResponse(const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
|
ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
|
||||||
Response(),
|
Response(verbose),
|
||||||
m_item(item),
|
m_item(item),
|
||||||
m_mimeType(mimetype)
|
m_mimeType(mimetype)
|
||||||
{
|
{
|
||||||
@@ -615,26 +446,30 @@ ItemResponse::ItemResponse(const zim::Item& item, const std::string& mimetype, c
|
|||||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> ItemResponse::build(const RequestContext& request, const zim::Item& item)
|
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item)
|
||||||
{
|
{
|
||||||
const std::string mimetype = get_mime_type(item);
|
const std::string mimetype = get_mime_type(item);
|
||||||
auto byteRange = request.get_range().resolve(item.getSize());
|
auto byteRange = request.get_range().resolve(item.getSize());
|
||||||
const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT;
|
const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT;
|
||||||
if (noRange && is_compressible_mime_type(mimetype)) {
|
if (noRange && is_compressible_mime_type(mimetype)) {
|
||||||
// Return a contentResponse
|
// Return a contentResponse
|
||||||
auto response = ContentResponse::build(item.getData(), mimetype);
|
auto response = ContentResponse::build(server, item.getData(), mimetype);
|
||||||
response->set_kind(Response::ZIM_CONTENT);
|
response->set_kind(Response::ZIM_CONTENT);
|
||||||
response->m_byteRange = byteRange;
|
response->m_byteRange = byteRange;
|
||||||
return std::move(response);
|
return std::move(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byteRange.kind() == ByteRange::RESOLVED_UNSATISFIABLE) {
|
if (byteRange.kind() == ByteRange::RESOLVED_UNSATISFIABLE) {
|
||||||
auto response = Response::build_416(item.getSize());
|
auto response = Response::build_416(server, item.getSize());
|
||||||
response->set_kind(Response::ZIM_CONTENT);
|
response->set_kind(Response::ZIM_CONTENT);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_unique<ItemResponse>(item, mimetype, byteRange);
|
return std::unique_ptr<Response>(new ItemResponse(
|
||||||
|
server.m_verbose.load(),
|
||||||
|
item,
|
||||||
|
mimetype,
|
||||||
|
byteRange));
|
||||||
}
|
}
|
||||||
|
|
||||||
MHD_Response*
|
MHD_Response*
|
||||||
|
|||||||
+66
-33
@@ -27,7 +27,7 @@
|
|||||||
#include <mustache.hpp>
|
#include <mustache.hpp>
|
||||||
#include "byte_range.h"
|
#include "byte_range.h"
|
||||||
#include "etag.h"
|
#include "etag.h"
|
||||||
#include "i18n_utils.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
#include <zim/item.h>
|
#include <zim/item.h>
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ class Archive;
|
|||||||
|
|
||||||
namespace kiwix {
|
namespace kiwix {
|
||||||
|
|
||||||
|
class InternalServer;
|
||||||
class RequestContext;
|
class RequestContext;
|
||||||
|
|
||||||
class Response {
|
class Response {
|
||||||
@@ -53,15 +54,15 @@ class Response {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Response();
|
Response(bool verbose);
|
||||||
virtual ~Response() = default;
|
virtual ~Response() = default;
|
||||||
|
|
||||||
static std::unique_ptr<Response> build();
|
static std::unique_ptr<Response> build(const InternalServer& server);
|
||||||
static std::unique_ptr<Response> build_304(const ETag& etag);
|
static std::unique_ptr<Response> build_304(const InternalServer& server, const ETag& etag);
|
||||||
static std::unique_ptr<Response> build_416(size_t resourceLength);
|
static std::unique_ptr<Response> build_416(const InternalServer& server, size_t resourceLength);
|
||||||
static std::unique_ptr<Response> build_redirect(const std::string& redirectUrl);
|
static std::unique_ptr<Response> build_redirect(const InternalServer& server, const std::string& redirectUrl);
|
||||||
|
|
||||||
MHD_Result send(const RequestContext& request, bool verbose, MHD_Connection* connection);
|
MHD_Result send(const RequestContext& request, MHD_Connection* connection);
|
||||||
|
|
||||||
void set_code(int code) { m_returnCode = code; }
|
void set_code(int code) { m_returnCode = code; }
|
||||||
void set_kind(Kind k);
|
void set_kind(Kind k);
|
||||||
@@ -77,6 +78,7 @@ class Response {
|
|||||||
|
|
||||||
protected: // data
|
protected: // data
|
||||||
Kind m_kind = DYNAMIC_CONTENT;
|
Kind m_kind = DYNAMIC_CONTENT;
|
||||||
|
bool m_verbose;
|
||||||
int m_returnCode;
|
int m_returnCode;
|
||||||
ByteRange m_byteRange;
|
ByteRange m_byteRange;
|
||||||
ETag m_etag;
|
ETag m_etag;
|
||||||
@@ -89,21 +91,22 @@ class Response {
|
|||||||
class ContentResponse : public Response {
|
class ContentResponse : public Response {
|
||||||
public:
|
public:
|
||||||
ContentResponse(
|
ContentResponse(
|
||||||
|
const std::string& root,
|
||||||
|
bool verbose,
|
||||||
const std::string& content,
|
const std::string& content,
|
||||||
const std::string& mimetype);
|
const std::string& mimetype);
|
||||||
|
|
||||||
static std::unique_ptr<ContentResponse> build(
|
static std::unique_ptr<ContentResponse> build(
|
||||||
|
const InternalServer& server,
|
||||||
const std::string& content,
|
const std::string& content,
|
||||||
const std::string& mimetype);
|
const std::string& mimetype);
|
||||||
|
|
||||||
static std::unique_ptr<ContentResponse> build(
|
static std::unique_ptr<ContentResponse> build(
|
||||||
|
const InternalServer& server,
|
||||||
const std::string& template_str,
|
const std::string& template_str,
|
||||||
kainjow::mustache::data data,
|
kainjow::mustache::data data,
|
||||||
const std::string& mimetype);
|
const std::string& mimetype);
|
||||||
|
|
||||||
const std::string& getContent() const { return m_content; }
|
|
||||||
const std::string& getMimeType() const { return m_mimeType; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||||
|
|
||||||
@@ -111,6 +114,7 @@ class ContentResponse : public Response {
|
|||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string m_root;
|
||||||
std::string m_content;
|
std::string m_content;
|
||||||
std::string m_mimeType;
|
std::string m_mimeType;
|
||||||
};
|
};
|
||||||
@@ -118,70 +122,99 @@ class ContentResponse : public Response {
|
|||||||
class ContentResponseBlueprint
|
class ContentResponseBlueprint
|
||||||
{
|
{
|
||||||
public: // functions
|
public: // functions
|
||||||
ContentResponseBlueprint(const RequestContext* request,
|
ContentResponseBlueprint(const InternalServer* server,
|
||||||
|
const RequestContext* request,
|
||||||
int httpStatusCode,
|
int httpStatusCode,
|
||||||
const std::string& mimeType,
|
const std::string& mimeType,
|
||||||
const std::string& templateStr,
|
const std::string& templateStr)
|
||||||
bool includeKiwixResponseData = false);
|
: m_server(*server)
|
||||||
|
, m_request(*request)
|
||||||
|
, m_httpStatusCode(httpStatusCode)
|
||||||
|
, m_mimeType(mimeType)
|
||||||
|
, m_template(templateStr)
|
||||||
|
{}
|
||||||
|
|
||||||
~ContentResponseBlueprint();
|
virtual ~ContentResponseBlueprint() = default;
|
||||||
|
|
||||||
operator std::unique_ptr<Response>() const
|
operator std::unique_ptr<ContentResponse>() const
|
||||||
{
|
{
|
||||||
return generateResponseObject();
|
return generateResponseObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentResponse> generateResponseObject() const;
|
operator std::unique_ptr<Response>() const
|
||||||
|
{
|
||||||
|
return operator std::unique_ptr<ContentResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
protected: // types
|
|
||||||
class Data;
|
|
||||||
|
|
||||||
protected: //data
|
protected: // functions
|
||||||
|
std::string getMessage(const std::string& msgId) const;
|
||||||
|
virtual std::unique_ptr<ContentResponse> generateResponseObject() const;
|
||||||
|
|
||||||
|
public: //data
|
||||||
|
const InternalServer& m_server;
|
||||||
const RequestContext& m_request;
|
const RequestContext& m_request;
|
||||||
const int m_httpStatusCode;
|
const int m_httpStatusCode;
|
||||||
const std::string m_mimeType;
|
const std::string m_mimeType;
|
||||||
const std::string m_template;
|
const std::string m_template;
|
||||||
const bool m_includeKiwixResponseData;
|
kainjow::mustache::data m_data;
|
||||||
std::unique_ptr<Data> m_data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HTTPErrorResponse : ContentResponseBlueprint
|
struct HTTPErrorResponse : ContentResponseBlueprint
|
||||||
{
|
{
|
||||||
HTTPErrorResponse(const RequestContext& request,
|
HTTPErrorResponse(const InternalServer& server,
|
||||||
|
const RequestContext& request,
|
||||||
int httpStatusCode,
|
int httpStatusCode,
|
||||||
const std::string& pageTitleMsgId,
|
const std::string& pageTitleMsgId,
|
||||||
const std::string& headingMsgId,
|
const std::string& headingMsgId,
|
||||||
const std::string& cssUrl = "",
|
const std::string& cssUrl = "");
|
||||||
bool includeKiwixResponseData = false);
|
|
||||||
|
|
||||||
|
HTTPErrorResponse& operator+(const std::string& msg);
|
||||||
HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails);
|
HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails);
|
||||||
HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails);
|
HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class UrlNotFoundMsg {};
|
||||||
|
|
||||||
|
extern const UrlNotFoundMsg urlNotFoundMsg;
|
||||||
|
|
||||||
struct HTTP404Response : HTTPErrorResponse
|
struct HTTP404Response : HTTPErrorResponse
|
||||||
{
|
{
|
||||||
explicit HTTP404Response(const RequestContext& request);
|
HTTP404Response(const InternalServer& server,
|
||||||
|
const RequestContext& request);
|
||||||
|
|
||||||
|
using HTTPErrorResponse::operator+;
|
||||||
|
HTTPErrorResponse& operator+(UrlNotFoundMsg /*unused*/);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UrlNotFoundResponse : HTTP404Response
|
class InvalidUrlMsg {};
|
||||||
{
|
|
||||||
explicit UrlNotFoundResponse(const RequestContext& request);
|
extern const InvalidUrlMsg invalidUrlMsg;
|
||||||
};
|
|
||||||
|
|
||||||
struct HTTP400Response : HTTPErrorResponse
|
struct HTTP400Response : HTTPErrorResponse
|
||||||
{
|
{
|
||||||
explicit HTTP400Response(const RequestContext& request);
|
HTTP400Response(const InternalServer& server,
|
||||||
|
const RequestContext& request);
|
||||||
|
|
||||||
|
using HTTPErrorResponse::operator+;
|
||||||
|
HTTPErrorResponse& operator+(InvalidUrlMsg /*unused*/);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HTTP500Response : HTTPErrorResponse
|
struct HTTP500Response : HTTPErrorResponse
|
||||||
{
|
{
|
||||||
explicit HTTP500Response(const RequestContext& request);
|
HTTP500Response(const InternalServer& server,
|
||||||
|
const RequestContext& request);
|
||||||
|
|
||||||
|
private: // overrides
|
||||||
|
// generateResponseObject() is overriden in order to produce a minimal
|
||||||
|
// response without any need for additional resources from the server
|
||||||
|
std::unique_ptr<ContentResponse> generateResponseObject() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ItemResponse : public Response {
|
class ItemResponse : public Response {
|
||||||
public:
|
public:
|
||||||
ItemResponse(const zim::Item& item, const std::string& mimetype, const ByteRange& byterange);
|
ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange);
|
||||||
static std::unique_ptr<Response> build(const RequestContext& request, const zim::Item& item);
|
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
#include "tools.h"
|
|
||||||
#include "stringTools.h"
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace kiwix
|
|
||||||
{
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
// These mappings are not provided by the ICU library, any such mappings can be manually added here
|
|
||||||
std::map<std::string, std::string> iso639_3 = {
|
|
||||||
{"ami", "Amis"},
|
|
||||||
{"atj", "atikamekw"},
|
|
||||||
{"azb", "آذربایجان دیلی"},
|
|
||||||
{"bcl", "central bikol"},
|
|
||||||
{"bgs", "tagabawa"},
|
|
||||||
{"blk", "ပအိုဝ်ႏ"},
|
|
||||||
{"bxr", "буряад хэлэн"},
|
|
||||||
{"cbk", "chavacano"},
|
|
||||||
{"cdo", "閩東語"},
|
|
||||||
{"dag", "Dagbani"},
|
|
||||||
{"diq", "dimli"},
|
|
||||||
{"dty", "डोटेली"},
|
|
||||||
{"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", "ဘာသာမန်"},
|
|
||||||
{"myn", "mayan"},
|
|
||||||
{"nah", "nahuatl"},
|
|
||||||
{"nai", "north American Indian"},
|
|
||||||
{"nds", "plattdütsch"},
|
|
||||||
{"nrm", "bhasa narom"},
|
|
||||||
{"olo", "livvi"},
|
|
||||||
{"pih", "Pitcairn-Norfolk"},
|
|
||||||
{"pnb", "Western Panjabi"},
|
|
||||||
{"pwn", "Pinayuanan"},
|
|
||||||
{"rmr", "Caló"},
|
|
||||||
{"rmy", "romani shib"},
|
|
||||||
{"roa", "romance languages"},
|
|
||||||
{"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"},
|
|
||||||
};
|
|
||||||
|
|
||||||
std::once_flag fillLanguagesFlag;
|
|
||||||
|
|
||||||
void fillLanguagesMap()
|
|
||||||
{
|
|
||||||
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
|
|
||||||
const kiwix::ICULanguageInfo lang(*icuLangPtr);
|
|
||||||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
|
||||||
}
|
|
||||||
iso639_3.erase("mul");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // unnamed namespace
|
|
||||||
|
|
||||||
std::string getLanguageSelfName(const std::string& lang)
|
|
||||||
{
|
|
||||||
std::call_once(fillLanguagesFlag, fillLanguagesMap);
|
|
||||||
const auto itr = iso639_3.find(lang);
|
|
||||||
if (itr != iso639_3.end()) {
|
|
||||||
return itr->second;
|
|
||||||
}
|
|
||||||
return lang;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kiwix
|
|
||||||
+84
-163
@@ -19,7 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "tools.h"
|
#include "tools.h"
|
||||||
#include "stringTools.h"
|
|
||||||
#include <tools/networkTools.h>
|
#include <tools/networkTools.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -33,29 +32,17 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <iphlpapi.h>
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#else
|
#else
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <arpa/inet.h>
|
#include <sys/ioctl.h>
|
||||||
#include <ifaddrs.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <net/if.h>
|
#include <net/if.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __HAIKU__
|
|
||||||
#include <sys/sockio.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace kiwix
|
|
||||||
{
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
|
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||||
{
|
{
|
||||||
auto str = static_cast<std::stringstream*>(userdata);
|
auto str = static_cast<std::stringstream*>(userdata);
|
||||||
@@ -63,15 +50,7 @@ size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdat
|
|||||||
return nmemb;
|
return nmemb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatePublicIpAddress(IpAddress& publicIpAddr, const IpAddress& interfaceIpAddr)
|
std::string kiwix::download(const std::string& url) {
|
||||||
{
|
|
||||||
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();
|
auto curl = curl_easy_init();
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
@@ -92,161 +71,103 @@ std::string download(const std::string& url) {
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
std::map<std::string, std::string> kiwix::getNetworkInterfaces() {
|
||||||
{
|
std::map<std::string, std::string> interfaces;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
|
||||||
std::map<std::string, IpAddress> getNetworkInterfacesWin() {
|
if (sd == INVALID_SOCKET) {
|
||||||
std::map<std::string, IpAddress> interfaces;
|
std::cerr << "Failed to get a socket. Error " << WSAGetLastError() << std::endl;
|
||||||
|
return interfaces;
|
||||||
const int working_buffer_size = 15000;
|
|
||||||
const int max_tries = 3;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interfacesHead) free(interfacesHead);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return interfaces;
|
return interfaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
std::string kiwix::getBestPublicIp() {
|
||||||
|
auto interfaces = getNetworkInterfaces();
|
||||||
|
|
||||||
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
|
#ifndef _WIN32
|
||||||
const char* const prioritizedNames[] = { "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
const char* const prioritizedNames[] =
|
||||||
for (const auto& name : prioritizedNames) {
|
{ "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
||||||
const auto it = interfaces.find(name);
|
for(auto name: prioritizedNames) {
|
||||||
if (it != interfaces.end()) {
|
auto it = interfaces.find(name);
|
||||||
updatePublicIpAddress(bestPublicIps, it->second);
|
if(it != interfaces.end()) {
|
||||||
|
return it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
const char* const v4prefixes[] = { "192.168", "172.16", "10.0" };
|
|
||||||
for (const auto& prefix : v4prefixes) {
|
const char* const prefixes[] = { "192.168", "172.16.", "10.0" };
|
||||||
for (const auto& kv : interfaces) {
|
for(auto prefix : prefixes){
|
||||||
const auto& interfaceIps = kv.second;
|
for(auto& itr : interfaces) {
|
||||||
if (kiwix::startsWith(interfaceIps.addr, prefix)) {
|
auto interfaceIp = itr.second;
|
||||||
updatePublicIpAddress(bestPublicIps, interfaceIps);
|
if (interfaceIp.find(prefix) == 0) {
|
||||||
|
return interfaceIp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePublicIpAddress(bestPublicIps, {"127.0.0.1", "::1"});
|
return "127.0.0.1";
|
||||||
|
|
||||||
return bestPublicIps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getBestPublicIp()
|
|
||||||
{
|
|
||||||
return getBestPublicIps().addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace kiwix
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#include "tools.h"
|
|
||||||
#include <pugixml.hpp>
|
|
||||||
|
|
||||||
namespace kiwix
|
|
||||||
{
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
#define VALUE(name) entryNode.child(name).child_value()
|
|
||||||
FeedLanguages parseLanguages(const pugi::xml_document& doc)
|
|
||||||
{
|
|
||||||
pugi::xml_node feedNode = doc.child("feed");
|
|
||||||
FeedLanguages langs;
|
|
||||||
|
|
||||||
for (pugi::xml_node entryNode = feedNode.child("entry"); entryNode;
|
|
||||||
entryNode = entryNode.next_sibling("entry")) {
|
|
||||||
auto title = VALUE("title");
|
|
||||||
auto code = VALUE("dc:language");
|
|
||||||
langs.push_back({code, title});
|
|
||||||
}
|
|
||||||
|
|
||||||
return langs;
|
|
||||||
}
|
|
||||||
|
|
||||||
FeedCategories parseCategories(const pugi::xml_document& doc)
|
|
||||||
{
|
|
||||||
pugi::xml_node feedNode = doc.child("feed");
|
|
||||||
FeedCategories categories;
|
|
||||||
|
|
||||||
for (pugi::xml_node entryNode = feedNode.child("entry"); entryNode;
|
|
||||||
entryNode = entryNode.next_sibling("entry")) {
|
|
||||||
auto title = VALUE("title");
|
|
||||||
categories.push_back(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
return categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // unnamed namespace
|
|
||||||
|
|
||||||
FeedLanguages readLanguagesFromFeed(const std::string& content)
|
|
||||||
{
|
|
||||||
pugi::xml_document doc;
|
|
||||||
pugi::xml_parse_result result
|
|
||||||
= doc.load_buffer((void*)content.data(), content.size());
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
auto langs = parseLanguages(doc);
|
|
||||||
return langs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FeedLanguages();
|
|
||||||
}
|
|
||||||
|
|
||||||
FeedCategories readCategoriesFromFeed(const std::string& content)
|
|
||||||
{
|
|
||||||
pugi::xml_document doc;
|
|
||||||
pugi::xml_parse_result result
|
|
||||||
= doc.load_buffer((void*)content.data(), content.size());
|
|
||||||
|
|
||||||
FeedCategories categories;
|
|
||||||
if (result) {
|
|
||||||
categories = parseCategories(doc);
|
|
||||||
return categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
return categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace kiwix
|
|
||||||
+19
-34
@@ -32,7 +32,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "tools/stringTools.h"
|
#include "tools/stringTools.h"
|
||||||
#include "server/i18n_utils.h"
|
#include "server/i18n.h"
|
||||||
#include "libkiwix-resources.h"
|
#include "libkiwix-resources.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -327,37 +327,22 @@ std::string kiwix::render_template(const std::string& template_str, kainjow::mus
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The escapeQuote parameter of escapeForJSON() defaults to true.
|
|
||||||
// This constant makes the calls to escapeForJSON() where the quote symbol
|
|
||||||
// should not be escaped (as it is later replaced with the HTML character entity
|
|
||||||
// ") more readable.
|
|
||||||
static const bool DONT_ESCAPE_QUOTE = false;
|
|
||||||
|
|
||||||
std::string kiwix::escapeForJSON(const std::string& s, bool escapeQuote)
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
for (char c : s) {
|
|
||||||
if ( c == '\\' ) {
|
|
||||||
oss << "\\\\";
|
|
||||||
} else if ( unsigned(c) < 0x20U ) {
|
|
||||||
switch ( c ) {
|
|
||||||
case '\n': oss << "\\n"; break;
|
|
||||||
case '\r': oss << "\\r"; break;
|
|
||||||
case '\t': oss << "\\t"; break;
|
|
||||||
default: oss << "\\u" << std::setw(4) << std::setfill('0') << unsigned(c);
|
|
||||||
}
|
|
||||||
} else if ( c == '"' && escapeQuote ) {
|
|
||||||
oss << "\\\"";
|
|
||||||
} else {
|
|
||||||
oss << c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return oss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
std::string escapeBackslashes(const std::string& s)
|
||||||
|
{
|
||||||
|
std::string es;
|
||||||
|
es.reserve(s.size());
|
||||||
|
for (char c : s) {
|
||||||
|
if ( c == '\\' ) {
|
||||||
|
es.push_back('\\');
|
||||||
|
}
|
||||||
|
es.push_back(c);
|
||||||
|
}
|
||||||
|
return es;
|
||||||
|
}
|
||||||
|
|
||||||
std::string makeFulltextSearchSuggestion(const std::string& lang,
|
std::string makeFulltextSearchSuggestion(const std::string& lang,
|
||||||
const std::string& queryString)
|
const std::string& queryString)
|
||||||
{
|
{
|
||||||
@@ -383,10 +368,10 @@ void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
|
|||||||
? suggestion.getSnippet()
|
? suggestion.getSnippet()
|
||||||
: suggestion.getTitle();
|
: suggestion.getTitle();
|
||||||
|
|
||||||
result.set("label", escapeForJSON(label, DONT_ESCAPE_QUOTE));
|
result.set("label", escapeBackslashes(label));
|
||||||
result.set("value", escapeForJSON(suggestion.getTitle(), DONT_ESCAPE_QUOTE));
|
result.set("value", escapeBackslashes(suggestion.getTitle()));
|
||||||
result.set("kind", "path");
|
result.set("kind", "path");
|
||||||
result.set("path", escapeForJSON(suggestion.getPath(), DONT_ESCAPE_QUOTE));
|
result.set("path", escapeBackslashes(suggestion.getPath()));
|
||||||
result.set("first", m_data.is_empty_list());
|
result.set("first", m_data.is_empty_list());
|
||||||
m_data.push_back(result);
|
m_data.push_back(result);
|
||||||
}
|
}
|
||||||
@@ -396,8 +381,8 @@ void kiwix::Suggestions::addFTSearchSuggestion(const std::string& uiLang,
|
|||||||
{
|
{
|
||||||
kainjow::mustache::data result;
|
kainjow::mustache::data result;
|
||||||
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
|
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
|
||||||
result.set("label", escapeForJSON(label, DONT_ESCAPE_QUOTE));
|
result.set("label", escapeBackslashes(label));
|
||||||
result.set("value", escapeForJSON(queryString + " ", DONT_ESCAPE_QUOTE));
|
result.set("value", escapeBackslashes(queryString + " "));
|
||||||
result.set("kind", "pattern");
|
result.set("kind", "pattern");
|
||||||
result.set("first", m_data.is_empty_list());
|
result.set("first", m_data.is_empty_list());
|
||||||
m_data.push_back(result);
|
m_data.push_back(result);
|
||||||
|
|||||||
@@ -320,6 +320,16 @@ bool kiwix::fileReadable(const std::string& path)
|
|||||||
#endif
|
#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()
|
std::string makeTmpDirectory()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -428,6 +438,52 @@ std::string kiwix::getCurrentDirectory()
|
|||||||
return ret;
|
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 = {
|
static std::map<std::string, std::string> extMimeTypes = {
|
||||||
{ "html", "text/html"},
|
{ "html", "text/html"},
|
||||||
{ "htm", "text/html"},
|
{ "htm", "text/html"},
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ std::wstring Utf8ToWide(const std::string& str);
|
|||||||
|
|
||||||
unsigned int getFileSize(const std::string& path);
|
unsigned int getFileSize(const std::string& path);
|
||||||
std::string getFileSizeAsString(const std::string& path);
|
std::string getFileSizeAsString(const std::string& path);
|
||||||
|
bool makeDirectory(const std::string& path);
|
||||||
std::string makeTmpDirectory();
|
std::string makeTmpDirectory();
|
||||||
bool copyFile(const std::string& sourcePath, const std::string& destPath);
|
bool copyFile(const std::string& sourcePath, const std::string& destPath);
|
||||||
bool writeTextFile(const std::string& path, const std::string& content);
|
bool writeTextFile(const std::string& path, const std::string& content);
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
/* tell ICU where to find its dat file (tables) */
|
/* tell ICU where to find its dat file (tables) */
|
||||||
void kiwix::loadICUExternalTables()
|
void kiwix::loadICUExternalTables()
|
||||||
@@ -257,7 +256,7 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
|
|||||||
|
|
||||||
// If there aren't enough characters left for this to be a
|
// If there aren't enough characters left for this to be a
|
||||||
// valid escape code, just use the character and move on
|
// valid escape code, just use the character and move on
|
||||||
if (value.end() - it < 3) {
|
if (it > value.end() - 3) {
|
||||||
os << *it;
|
os << *it;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -440,13 +439,3 @@ template<>
|
|||||||
std::string kiwix::extractFromString(const std::string& str) {
|
std::string kiwix::extractFromString(const std::string& str) {
|
||||||
return 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, "_");
|
|
||||||
}
|
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
std::string beautifyInteger(uint64_t number);
|
std::string beautifyInteger(uint64_t number);
|
||||||
|
std::string beautifyFileSize(uint64_t number);
|
||||||
void printStringInHexadecimal(const char* s);
|
void printStringInHexadecimal(const char* s);
|
||||||
void printStringInHexadecimal(icu::UnicodeString s);
|
void printStringInHexadecimal(icu::UnicodeString s);
|
||||||
void stringReplacement(std::string& str,
|
void stringReplacement(std::string& str,
|
||||||
@@ -53,7 +54,6 @@ private:
|
|||||||
const icu::Locale locale;
|
const icu::Locale locale;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string escapeForJSON(const std::string& s, bool escapeQuote = true);
|
|
||||||
|
|
||||||
/* urlEncode() is the equivalent of JS encodeURIComponent(), with the only
|
/* urlEncode() is the equivalent of JS encodeURIComponent(), with the only
|
||||||
* difference that the slash (/) symbol is NOT encoded. */
|
* difference that the slash (/) symbol is NOT encoded. */
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ def get_translation_info(filepath):
|
|||||||
with open(filepath, 'r', encoding="utf-8") as f:
|
with open(filepath, 'r', encoding="utf-8") as f:
|
||||||
content = json.load(f)
|
content = json.load(f)
|
||||||
lang_name = content.get("name")
|
lang_name = content.get("name")
|
||||||
translation_count = len(content)
|
return lang_code, lang_name
|
||||||
return dict(iso_code=lang_code,
|
|
||||||
self_name=lang_name,
|
|
||||||
translation_count=translation_count)
|
|
||||||
|
|
||||||
language_list = []
|
language_list = []
|
||||||
json_files = translation_dir.glob("*.json")
|
json_files = translation_dir.glob("*.json")
|
||||||
@@ -43,14 +40,14 @@ with open(resource_file, 'w', encoding="utf-8") as f:
|
|||||||
continue
|
continue
|
||||||
print("Processing", i18n_file.name)
|
print("Processing", i18n_file.name)
|
||||||
if i18n_file.name != "test.json":
|
if i18n_file.name != "test.json":
|
||||||
translation_info = get_translation_info(i18n_file)
|
lang_code, lang_name = get_translation_info(i18n_file)
|
||||||
lang_name = translation_info["self_name"]
|
|
||||||
if lang_name:
|
if lang_name:
|
||||||
language_list.append(translation_info)
|
language_list.append((lang_code, lang_name))
|
||||||
else:
|
else:
|
||||||
print(f"Warning: missing 'name' in {i18n_file.name}")
|
print(f"Warning: missing 'name' in {i18n_file.name}")
|
||||||
f.write(str(i18n_file.relative_to(script_path.parent)) + '\n')
|
f.write(str(i18n_file.relative_to(script_path.parent)) + '\n')
|
||||||
|
|
||||||
|
language_list = [{name: code} for code, name in sorted(language_list)]
|
||||||
language_list_jsobj_str = json.dumps(language_list,
|
language_list_jsobj_str = json.dumps(language_list,
|
||||||
indent=2,
|
indent=2,
|
||||||
ensure_ascii=False)
|
ensure_ascii=False)
|
||||||
|
|||||||
@@ -1,42 +1,28 @@
|
|||||||
skin/i18n/ar.json
|
skin/i18n/ar.json
|
||||||
skin/i18n/bn.json
|
skin/i18n/bn.json
|
||||||
skin/i18n/br.json
|
|
||||||
skin/i18n/cs.json
|
skin/i18n/cs.json
|
||||||
skin/i18n/dag.json
|
|
||||||
skin/i18n/de.json
|
skin/i18n/de.json
|
||||||
skin/i18n/dga.json
|
skin/i18n/dga.json
|
||||||
skin/i18n/el.json
|
skin/i18n/el.json
|
||||||
skin/i18n/en.json
|
skin/i18n/en.json
|
||||||
skin/i18n/es.json
|
|
||||||
skin/i18n/fi.json
|
|
||||||
skin/i18n/fr.json
|
skin/i18n/fr.json
|
||||||
skin/i18n/ha.json
|
|
||||||
skin/i18n/he.json
|
skin/i18n/he.json
|
||||||
skin/i18n/hi.json
|
|
||||||
skin/i18n/hy.json
|
skin/i18n/hy.json
|
||||||
skin/i18n/ia.json
|
skin/i18n/ia.json
|
||||||
skin/i18n/ig.json
|
|
||||||
skin/i18n/it.json
|
skin/i18n/it.json
|
||||||
skin/i18n/ja.json
|
skin/i18n/ja.json
|
||||||
skin/i18n/ko.json
|
skin/i18n/ko.json
|
||||||
skin/i18n/ku-latn.json
|
skin/i18n/ku-latn.json
|
||||||
skin/i18n/lb.json
|
skin/i18n/lb.json
|
||||||
skin/i18n/mk.json
|
skin/i18n/mk.json
|
||||||
skin/i18n/ms.json
|
|
||||||
skin/i18n/nl.json
|
skin/i18n/nl.json
|
||||||
skin/i18n/nqo.json
|
skin/i18n/nqo.json
|
||||||
skin/i18n/or.json
|
|
||||||
skin/i18n/pl.json
|
skin/i18n/pl.json
|
||||||
skin/i18n/pt-br.json
|
|
||||||
skin/i18n/ru.json
|
skin/i18n/ru.json
|
||||||
skin/i18n/sc.json
|
skin/i18n/sc.json
|
||||||
skin/i18n/sk.json
|
skin/i18n/sk.json
|
||||||
skin/i18n/skr-arab.json
|
|
||||||
skin/i18n/sl.json
|
skin/i18n/sl.json
|
||||||
skin/i18n/sq.json
|
|
||||||
skin/i18n/sv.json
|
skin/i18n/sv.json
|
||||||
skin/i18n/sw.json
|
|
||||||
skin/i18n/te.json
|
|
||||||
skin/i18n/test.json
|
skin/i18n/test.json
|
||||||
skin/i18n/tr.json
|
skin/i18n/tr.json
|
||||||
skin/i18n/zh-hans.json
|
skin/i18n/zh-hans.json
|
||||||
|
|||||||
@@ -4,21 +4,18 @@ skin/magnet.png
|
|||||||
skin/feed.svg
|
skin/feed.svg
|
||||||
skin/langSelector.svg
|
skin/langSelector.svg
|
||||||
skin/download.png
|
skin/download.png
|
||||||
skin/download-white.svg
|
|
||||||
skin/hash.png
|
skin/hash.png
|
||||||
skin/search-icon.svg
|
skin/search-icon.svg
|
||||||
skin/iso6391To3.js
|
skin/iso6391To3.js
|
||||||
skin/isotope.pkgd.min.js
|
skin/isotope.pkgd.min.js
|
||||||
skin/index.js
|
skin/index.js
|
||||||
skin/autoComplete/autoComplete.min.js
|
skin/autoComplete.min.js
|
||||||
skin/kiwix.css
|
|
||||||
skin/taskbar.css
|
skin/taskbar.css
|
||||||
skin/index.css
|
skin/index.css
|
||||||
skin/fonts/Poppins.ttf
|
skin/fonts/Poppins.ttf
|
||||||
skin/fonts/Roboto.ttf
|
skin/fonts/Roboto.ttf
|
||||||
skin/search_results.css
|
skin/search_results.css
|
||||||
skin/blank.html
|
skin/blank.html
|
||||||
skin/polyfills.js
|
|
||||||
skin/viewer.js
|
skin/viewer.js
|
||||||
skin/i18n.js
|
skin/i18n.js
|
||||||
skin/languages.js
|
skin/languages.js
|
||||||
@@ -38,14 +35,14 @@ templates/catalog_v2_entry.xml
|
|||||||
templates/catalog_v2_partial_entry.xml
|
templates/catalog_v2_partial_entry.xml
|
||||||
templates/catalog_v2_categories.xml
|
templates/catalog_v2_categories.xml
|
||||||
templates/catalog_v2_languages.xml
|
templates/catalog_v2_languages.xml
|
||||||
templates/url_of_search_results_css.tmpl
|
templates/url_of_search_results_css
|
||||||
templates/viewer_settings.js
|
templates/viewer_settings.js
|
||||||
templates/no_js_library_page.html
|
templates/no_js_library_page.html
|
||||||
templates/no_js_download.html
|
templates/no_js_download.html
|
||||||
opensearchdescription.xml
|
opensearchdescription.xml
|
||||||
ft_opensearchdescription.xml
|
ft_opensearchdescription.xml
|
||||||
catalog_v2_searchdescription.xml
|
catalog_v2_searchdescription.xml
|
||||||
skin/autoComplete/css/autoComplete.css
|
skin/css/autoComplete.css
|
||||||
skin/favicon/android-chrome-192x192.png
|
skin/favicon/android-chrome-192x192.png
|
||||||
skin/favicon/android-chrome-512x512.png
|
skin/favicon/android-chrome-512x512.png
|
||||||
skin/favicon/apple-touch-icon.png
|
skin/favicon/apple-touch-icon.png
|
||||||
|
|||||||
File renamed without changes.
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1,654 +0,0 @@
|
|||||||
(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
||||||
typeof define === 'function' && define.amd ? define(factory) :
|
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.autoComplete = factory());
|
|
||||||
}(this, (function () { 'use strict';
|
|
||||||
|
|
||||||
function ownKeys(object, enumerableOnly) {
|
|
||||||
var keys = Object.keys(object);
|
|
||||||
|
|
||||||
if (Object.getOwnPropertySymbols) {
|
|
||||||
var symbols = Object.getOwnPropertySymbols(object);
|
|
||||||
|
|
||||||
if (enumerableOnly) {
|
|
||||||
symbols = symbols.filter(function (sym) {
|
|
||||||
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.push.apply(keys, symbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _objectSpread2(target) {
|
|
||||||
for (var i = 1; i < arguments.length; i++) {
|
|
||||||
var source = arguments[i] != null ? arguments[i] : {};
|
|
||||||
|
|
||||||
if (i % 2) {
|
|
||||||
ownKeys(Object(source), true).forEach(function (key) {
|
|
||||||
_defineProperty(target, key, source[key]);
|
|
||||||
});
|
|
||||||
} else if (Object.getOwnPropertyDescriptors) {
|
|
||||||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
||||||
} else {
|
|
||||||
ownKeys(Object(source)).forEach(function (key) {
|
|
||||||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _typeof(obj) {
|
|
||||||
"@babel/helpers - typeof";
|
|
||||||
|
|
||||||
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
||||||
_typeof = function (obj) {
|
|
||||||
return typeof obj;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
_typeof = function (obj) {
|
|
||||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return _typeof(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _defineProperty(obj, key, value) {
|
|
||||||
if (key in obj) {
|
|
||||||
Object.defineProperty(obj, key, {
|
|
||||||
value: value,
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
obj[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _toConsumableArray(arr) {
|
|
||||||
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
|
|
||||||
}
|
|
||||||
|
|
||||||
function _arrayWithoutHoles(arr) {
|
|
||||||
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _iterableToArray(iter) {
|
|
||||||
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _unsupportedIterableToArray(o, minLen) {
|
|
||||||
if (!o) return;
|
|
||||||
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
|
|
||||||
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
||||||
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
||||||
if (n === "Map" || n === "Set") return Array.from(o);
|
|
||||||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _arrayLikeToArray(arr, len) {
|
|
||||||
if (len == null || len > arr.length) len = arr.length;
|
|
||||||
|
|
||||||
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
|
|
||||||
|
|
||||||
return arr2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _nonIterableSpread() {
|
|
||||||
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createForOfIteratorHelper(o, allowArrayLike) {
|
|
||||||
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
|
|
||||||
|
|
||||||
if (!it) {
|
|
||||||
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
|
|
||||||
if (it) o = it;
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
var F = function () {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
s: F,
|
|
||||||
n: function () {
|
|
||||||
if (i >= o.length) return {
|
|
||||||
done: true
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
done: false,
|
|
||||||
value: o[i++]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
e: function (e) {
|
|
||||||
throw e;
|
|
||||||
},
|
|
||||||
f: F
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalCompletion = true,
|
|
||||||
didErr = false,
|
|
||||||
err;
|
|
||||||
return {
|
|
||||||
s: function () {
|
|
||||||
it = it.call(o);
|
|
||||||
},
|
|
||||||
n: function () {
|
|
||||||
var step = it.next();
|
|
||||||
normalCompletion = step.done;
|
|
||||||
return step;
|
|
||||||
},
|
|
||||||
e: function (e) {
|
|
||||||
didErr = true;
|
|
||||||
err = e;
|
|
||||||
},
|
|
||||||
f: function () {
|
|
||||||
try {
|
|
||||||
if (!normalCompletion && it.return != null) it.return();
|
|
||||||
} finally {
|
|
||||||
if (didErr) throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var select$1 = function select(element) {
|
|
||||||
return typeof element === "string" ? document.querySelector(element) : element();
|
|
||||||
};
|
|
||||||
var create = function create(tag, options) {
|
|
||||||
var el = typeof tag === "string" ? document.createElement(tag) : tag;
|
|
||||||
for (var key in options) {
|
|
||||||
var val = options[key];
|
|
||||||
if (key === "inside") {
|
|
||||||
val.append(el);
|
|
||||||
} else if (key === "dest") {
|
|
||||||
select$1(val[0]).insertAdjacentElement(val[1], el);
|
|
||||||
} else if (key === "around") {
|
|
||||||
var ref = val;
|
|
||||||
ref.parentNode.insertBefore(el, ref);
|
|
||||||
el.append(ref);
|
|
||||||
if (ref.getAttribute("autofocus") != null) ref.focus();
|
|
||||||
} else if (key in el) {
|
|
||||||
el[key] = val;
|
|
||||||
} else {
|
|
||||||
el.setAttribute(key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
var getQuery = function getQuery(field) {
|
|
||||||
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement ? field.value : field.innerHTML;
|
|
||||||
};
|
|
||||||
var format = function format(value, diacritics) {
|
|
||||||
value = value.toString().toLowerCase();
|
|
||||||
return diacritics ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").normalize("NFC") : value;
|
|
||||||
};
|
|
||||||
var debounce = function debounce(callback, duration) {
|
|
||||||
var timer;
|
|
||||||
return function () {
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(function () {
|
|
||||||
return callback();
|
|
||||||
}, duration);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
var checkTrigger = function checkTrigger(query, condition, threshold) {
|
|
||||||
return condition ? condition(query) : query.length >= threshold;
|
|
||||||
};
|
|
||||||
var mark = function mark(value, cls) {
|
|
||||||
return create("mark", _objectSpread2({
|
|
||||||
innerHTML: value
|
|
||||||
}, typeof cls === "string" && {
|
|
||||||
"class": cls
|
|
||||||
})).outerHTML;
|
|
||||||
};
|
|
||||||
|
|
||||||
var configure = (function (ctx) {
|
|
||||||
var name = ctx.name,
|
|
||||||
options = ctx.options,
|
|
||||||
resultsList = ctx.resultsList,
|
|
||||||
resultItem = ctx.resultItem;
|
|
||||||
for (var option in options) {
|
|
||||||
if (_typeof(options[option]) === "object") {
|
|
||||||
if (!ctx[option]) ctx[option] = {};
|
|
||||||
for (var subOption in options[option]) {
|
|
||||||
ctx[option][subOption] = options[option][subOption];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx[option] = options[option];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.selector = ctx.selector || "#" + name;
|
|
||||||
resultsList.destination = resultsList.destination || ctx.selector;
|
|
||||||
resultsList.id = resultsList.id || name + "_list_" + ctx.id;
|
|
||||||
resultItem.id = resultItem.id || name + "_result";
|
|
||||||
ctx.input = select$1(ctx.selector);
|
|
||||||
});
|
|
||||||
|
|
||||||
var eventEmitter = (function (name, ctx) {
|
|
||||||
ctx.input.dispatchEvent(new CustomEvent(name, {
|
|
||||||
bubbles: true,
|
|
||||||
detail: ctx.feedback,
|
|
||||||
cancelable: true
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
var search = (function (query, record, options) {
|
|
||||||
var _ref = options || {},
|
|
||||||
mode = _ref.mode,
|
|
||||||
diacritics = _ref.diacritics,
|
|
||||||
highlight = _ref.highlight;
|
|
||||||
var nRecord = format(record, diacritics);
|
|
||||||
record = record.toString();
|
|
||||||
query = format(query, diacritics);
|
|
||||||
if (mode === "loose") {
|
|
||||||
query = query.replace(/ /g, "");
|
|
||||||
var qLength = query.length;
|
|
||||||
var cursor = 0;
|
|
||||||
var match = Array.from(record).map(function (character, index) {
|
|
||||||
if (cursor < qLength && nRecord[index] === query[cursor]) {
|
|
||||||
character = highlight ? mark(character, highlight) : character;
|
|
||||||
cursor++;
|
|
||||||
}
|
|
||||||
return character;
|
|
||||||
}).join("");
|
|
||||||
if (cursor === qLength) return match;
|
|
||||||
} else {
|
|
||||||
var _match = nRecord.indexOf(query);
|
|
||||||
if (~_match) {
|
|
||||||
query = record.substring(_match, _match + query.length);
|
|
||||||
_match = highlight ? record.replace(query, mark(query, highlight)) : record;
|
|
||||||
return _match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var getData = function getData(ctx, query) {
|
|
||||||
return new Promise(function ($return, $error) {
|
|
||||||
var data;
|
|
||||||
data = ctx.data;
|
|
||||||
if (data.cache && data.store) return $return();
|
|
||||||
return new Promise(function ($return, $error) {
|
|
||||||
if (typeof data.src === "function") {
|
|
||||||
return data.src(query).then($return, $error);
|
|
||||||
}
|
|
||||||
return $return(data.src);
|
|
||||||
}).then(function ($await_4) {
|
|
||||||
try {
|
|
||||||
ctx.feedback = data.store = $await_4;
|
|
||||||
eventEmitter("response", ctx);
|
|
||||||
return $return();
|
|
||||||
} catch ($boundEx) {
|
|
||||||
return $error($boundEx);
|
|
||||||
}
|
|
||||||
}, $error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var findMatches = function findMatches(query, ctx) {
|
|
||||||
var data = ctx.data,
|
|
||||||
searchEngine = ctx.searchEngine;
|
|
||||||
var matches = [];
|
|
||||||
data.store.forEach(function (value, index) {
|
|
||||||
var find = function find(key) {
|
|
||||||
var record = key ? value[key] : value;
|
|
||||||
var match = typeof searchEngine === "function" ? searchEngine(query, record) : search(query, record, {
|
|
||||||
mode: searchEngine,
|
|
||||||
diacritics: ctx.diacritics,
|
|
||||||
highlight: ctx.resultItem.highlight
|
|
||||||
});
|
|
||||||
if (!match) return;
|
|
||||||
var result = {
|
|
||||||
match: match,
|
|
||||||
value: value
|
|
||||||
};
|
|
||||||
if (key) result.key = key;
|
|
||||||
matches.push(result);
|
|
||||||
};
|
|
||||||
if (data.keys) {
|
|
||||||
var _iterator = _createForOfIteratorHelper(data.keys),
|
|
||||||
_step;
|
|
||||||
try {
|
|
||||||
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
||||||
var key = _step.value;
|
|
||||||
find(key);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
_iterator.e(err);
|
|
||||||
} finally {
|
|
||||||
_iterator.f();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
find();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (data.filter) matches = data.filter(matches);
|
|
||||||
var results = matches.slice(0, ctx.resultsList.maxResults);
|
|
||||||
ctx.feedback = {
|
|
||||||
query: query,
|
|
||||||
matches: matches,
|
|
||||||
results: results
|
|
||||||
};
|
|
||||||
eventEmitter("results", ctx);
|
|
||||||
};
|
|
||||||
|
|
||||||
var Expand = "aria-expanded";
|
|
||||||
var Active = "aria-activedescendant";
|
|
||||||
var Selected = "aria-selected";
|
|
||||||
var feedback = function feedback(ctx, index) {
|
|
||||||
ctx.feedback.selection = _objectSpread2({
|
|
||||||
index: index
|
|
||||||
}, ctx.feedback.results[index]);
|
|
||||||
};
|
|
||||||
var render = function render(ctx) {
|
|
||||||
var resultsList = ctx.resultsList,
|
|
||||||
list = ctx.list,
|
|
||||||
resultItem = ctx.resultItem,
|
|
||||||
feedback = ctx.feedback;
|
|
||||||
var matches = feedback.matches,
|
|
||||||
results = feedback.results;
|
|
||||||
ctx.cursor = -1;
|
|
||||||
list.innerHTML = "";
|
|
||||||
if (matches.length || resultsList.noResults) {
|
|
||||||
var fragment = new DocumentFragment();
|
|
||||||
results.forEach(function (result, index) {
|
|
||||||
var element = create(resultItem.tag, _objectSpread2({
|
|
||||||
id: "".concat(resultItem.id, "_").concat(index),
|
|
||||||
role: "option",
|
|
||||||
innerHTML: result.match,
|
|
||||||
inside: fragment
|
|
||||||
}, resultItem["class"] && {
|
|
||||||
"class": resultItem["class"]
|
|
||||||
}));
|
|
||||||
if (resultItem.element) resultItem.element(element, result);
|
|
||||||
});
|
|
||||||
list.append(fragment);
|
|
||||||
if (resultsList.element) resultsList.element(list, feedback);
|
|
||||||
open(ctx);
|
|
||||||
} else {
|
|
||||||
close(ctx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var open = function open(ctx) {
|
|
||||||
if (ctx.isOpen) return;
|
|
||||||
(ctx.wrapper || ctx.input).setAttribute(Expand, true);
|
|
||||||
ctx.list.removeAttribute("hidden");
|
|
||||||
ctx.isOpen = true;
|
|
||||||
eventEmitter("open", ctx);
|
|
||||||
};
|
|
||||||
var close = function close(ctx) {
|
|
||||||
if (!ctx.isOpen) return;
|
|
||||||
(ctx.wrapper || ctx.input).setAttribute(Expand, false);
|
|
||||||
ctx.input.setAttribute(Active, "");
|
|
||||||
ctx.list.setAttribute("hidden", "");
|
|
||||||
ctx.isOpen = false;
|
|
||||||
eventEmitter("close", ctx);
|
|
||||||
};
|
|
||||||
var goTo = function goTo(index, ctx) {
|
|
||||||
var resultItem = ctx.resultItem;
|
|
||||||
var results = ctx.list.getElementsByTagName(resultItem.tag);
|
|
||||||
var cls = resultItem.selected ? resultItem.selected.split(" ") : false;
|
|
||||||
if (ctx.isOpen && results.length) {
|
|
||||||
var _results$index$classL;
|
|
||||||
var state = ctx.cursor;
|
|
||||||
if (index >= results.length) index = 0;
|
|
||||||
if (index < 0) index = results.length - 1;
|
|
||||||
ctx.cursor = index;
|
|
||||||
if (state > -1) {
|
|
||||||
var _results$state$classL;
|
|
||||||
results[state].removeAttribute(Selected);
|
|
||||||
if (cls) (_results$state$classL = results[state].classList).remove.apply(_results$state$classL, _toConsumableArray(cls));
|
|
||||||
}
|
|
||||||
results[index].setAttribute(Selected, true);
|
|
||||||
if (cls) (_results$index$classL = results[index].classList).add.apply(_results$index$classL, _toConsumableArray(cls));
|
|
||||||
ctx.input.setAttribute(Active, results[ctx.cursor].id);
|
|
||||||
ctx.list.scrollTop = results[index].offsetTop - ctx.list.clientHeight + results[index].clientHeight + 5;
|
|
||||||
ctx.feedback.cursor = ctx.cursor;
|
|
||||||
feedback(ctx, index);
|
|
||||||
eventEmitter("navigate", ctx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var next = function next(ctx) {
|
|
||||||
goTo(ctx.cursor + 1, ctx);
|
|
||||||
};
|
|
||||||
var previous = function previous(ctx) {
|
|
||||||
goTo(ctx.cursor - 1, ctx);
|
|
||||||
};
|
|
||||||
var select = function select(ctx, event, index) {
|
|
||||||
index = index >= 0 ? index : ctx.cursor;
|
|
||||||
if (index < 0) return;
|
|
||||||
ctx.feedback.event = event;
|
|
||||||
feedback(ctx, index);
|
|
||||||
eventEmitter("selection", ctx);
|
|
||||||
close(ctx);
|
|
||||||
};
|
|
||||||
var click = function click(event, ctx) {
|
|
||||||
var itemTag = ctx.resultItem.tag.toUpperCase();
|
|
||||||
var items = Array.from(ctx.list.querySelectorAll(itemTag));
|
|
||||||
var item = event.target.closest(itemTag);
|
|
||||||
if (item && item.nodeName === itemTag) {
|
|
||||||
select(ctx, event, items.indexOf(item));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var navigate = function navigate(event, ctx) {
|
|
||||||
switch (event.keyCode) {
|
|
||||||
case 40:
|
|
||||||
case 38:
|
|
||||||
event.preventDefault();
|
|
||||||
event.keyCode === 40 ? next(ctx) : previous(ctx);
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
if (!ctx.submit) event.preventDefault();
|
|
||||||
if (ctx.cursor >= 0) select(ctx, event);
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
if (ctx.resultsList.tabSelect && ctx.cursor >= 0) select(ctx, event);
|
|
||||||
break;
|
|
||||||
case 27:
|
|
||||||
ctx.input.value = "";
|
|
||||||
close(ctx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function start (ctx, q) {
|
|
||||||
var _this = this;
|
|
||||||
return new Promise(function ($return, $error) {
|
|
||||||
var queryVal, condition;
|
|
||||||
queryVal = q || getQuery(ctx.input);
|
|
||||||
queryVal = ctx.query ? ctx.query(queryVal) : queryVal;
|
|
||||||
condition = checkTrigger(queryVal, ctx.trigger, ctx.threshold);
|
|
||||||
if (condition) {
|
|
||||||
return getData(ctx, queryVal).then(function ($await_2) {
|
|
||||||
try {
|
|
||||||
if (ctx.feedback instanceof Error) return $return();
|
|
||||||
findMatches(queryVal, ctx);
|
|
||||||
if (ctx.resultsList) render(ctx);
|
|
||||||
return $If_1.call(_this);
|
|
||||||
} catch ($boundEx) {
|
|
||||||
return $error($boundEx);
|
|
||||||
}
|
|
||||||
}, $error);
|
|
||||||
} else {
|
|
||||||
close(ctx);
|
|
||||||
return $If_1.call(_this);
|
|
||||||
}
|
|
||||||
function $If_1() {
|
|
||||||
return $return();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var eventsManager = function eventsManager(events, callback) {
|
|
||||||
for (var element in events) {
|
|
||||||
for (var event in events[element]) {
|
|
||||||
callback(element, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var addEvents = function addEvents(ctx) {
|
|
||||||
var events = ctx.events;
|
|
||||||
var run = debounce(function () {
|
|
||||||
return start(ctx);
|
|
||||||
}, ctx.debounce);
|
|
||||||
var publicEvents = ctx.events = _objectSpread2({
|
|
||||||
input: _objectSpread2({}, events && events.input)
|
|
||||||
}, ctx.resultsList && {
|
|
||||||
list: events ? _objectSpread2({}, events.list) : {}
|
|
||||||
});
|
|
||||||
var privateEvents = {
|
|
||||||
input: {
|
|
||||||
input: function input() {
|
|
||||||
run();
|
|
||||||
},
|
|
||||||
keydown: function keydown(event) {
|
|
||||||
navigate(event, ctx);
|
|
||||||
},
|
|
||||||
blur: function blur() {
|
|
||||||
close(ctx);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
mousedown: function mousedown(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
},
|
|
||||||
click: function click$1(event) {
|
|
||||||
click(event, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
eventsManager(privateEvents, function (element, event) {
|
|
||||||
if (!ctx.resultsList && event !== "input") return;
|
|
||||||
if (publicEvents[element][event]) return;
|
|
||||||
publicEvents[element][event] = privateEvents[element][event];
|
|
||||||
});
|
|
||||||
eventsManager(publicEvents, function (element, event) {
|
|
||||||
ctx[element].addEventListener(event, publicEvents[element][event]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var removeEvents = function removeEvents(ctx) {
|
|
||||||
eventsManager(ctx.events, function (element, event) {
|
|
||||||
ctx[element].removeEventListener(event, ctx.events[element][event]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function init (ctx) {
|
|
||||||
var _this = this;
|
|
||||||
return new Promise(function ($return, $error) {
|
|
||||||
var placeHolder, resultsList, parentAttrs;
|
|
||||||
placeHolder = ctx.placeHolder;
|
|
||||||
resultsList = ctx.resultsList;
|
|
||||||
parentAttrs = {
|
|
||||||
role: "combobox",
|
|
||||||
"aria-owns": resultsList.id,
|
|
||||||
"aria-haspopup": true,
|
|
||||||
"aria-expanded": false
|
|
||||||
};
|
|
||||||
create(ctx.input, _objectSpread2(_objectSpread2({
|
|
||||||
"aria-controls": resultsList.id,
|
|
||||||
"aria-autocomplete": "both"
|
|
||||||
}, placeHolder && {
|
|
||||||
placeholder: placeHolder
|
|
||||||
}), !ctx.wrapper && _objectSpread2({}, parentAttrs)));
|
|
||||||
if (ctx.wrapper) ctx.wrapper = create("div", _objectSpread2({
|
|
||||||
around: ctx.input,
|
|
||||||
"class": ctx.name + "_wrapper"
|
|
||||||
}, parentAttrs));
|
|
||||||
if (resultsList) ctx.list = create(resultsList.tag, _objectSpread2({
|
|
||||||
dest: [resultsList.destination, resultsList.position],
|
|
||||||
id: resultsList.id,
|
|
||||||
role: "listbox",
|
|
||||||
hidden: "hidden"
|
|
||||||
}, resultsList["class"] && {
|
|
||||||
"class": resultsList["class"]
|
|
||||||
}));
|
|
||||||
addEvents(ctx);
|
|
||||||
if (ctx.data.cache) {
|
|
||||||
return getData(ctx).then(function ($await_2) {
|
|
||||||
try {
|
|
||||||
return $If_1.call(_this);
|
|
||||||
} catch ($boundEx) {
|
|
||||||
return $error($boundEx);
|
|
||||||
}
|
|
||||||
}, $error);
|
|
||||||
}
|
|
||||||
function $If_1() {
|
|
||||||
eventEmitter("init", ctx);
|
|
||||||
return $return();
|
|
||||||
}
|
|
||||||
return $If_1.call(_this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function extend (autoComplete) {
|
|
||||||
var prototype = autoComplete.prototype;
|
|
||||||
prototype.init = function () {
|
|
||||||
init(this);
|
|
||||||
};
|
|
||||||
prototype.start = function (query) {
|
|
||||||
start(this, query);
|
|
||||||
};
|
|
||||||
prototype.unInit = function () {
|
|
||||||
if (this.wrapper) {
|
|
||||||
var parentNode = this.wrapper.parentNode;
|
|
||||||
parentNode.insertBefore(this.input, this.wrapper);
|
|
||||||
parentNode.removeChild(this.wrapper);
|
|
||||||
}
|
|
||||||
removeEvents(this);
|
|
||||||
};
|
|
||||||
prototype.open = function () {
|
|
||||||
open(this);
|
|
||||||
};
|
|
||||||
prototype.close = function () {
|
|
||||||
close(this);
|
|
||||||
};
|
|
||||||
prototype.goTo = function (index) {
|
|
||||||
goTo(index, this);
|
|
||||||
};
|
|
||||||
prototype.next = function () {
|
|
||||||
next(this);
|
|
||||||
};
|
|
||||||
prototype.previous = function () {
|
|
||||||
previous(this);
|
|
||||||
};
|
|
||||||
prototype.select = function (index) {
|
|
||||||
select(this, null, index);
|
|
||||||
};
|
|
||||||
prototype.search = function (query, record, options) {
|
|
||||||
return search(query, record, options);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoComplete(config) {
|
|
||||||
this.options = config;
|
|
||||||
this.id = autoComplete.instances = (autoComplete.instances || 0) + 1;
|
|
||||||
this.name = "autoComplete";
|
|
||||||
this.wrapper = 1;
|
|
||||||
this.threshold = 1;
|
|
||||||
this.debounce = 0;
|
|
||||||
this.resultsList = {
|
|
||||||
position: "afterend",
|
|
||||||
tag: "ul",
|
|
||||||
maxResults: 5
|
|
||||||
};
|
|
||||||
this.resultItem = {
|
|
||||||
tag: "li"
|
|
||||||
};
|
|
||||||
configure(this);
|
|
||||||
extend.call(this, autoComplete);
|
|
||||||
init(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return autoComplete;
|
|
||||||
|
|
||||||
})));
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
/* Modified from https://github.com/TarekRaafat/autoComplete.js (version 10.2.6)*/
|
|
||||||
.autoComplete_wrapper {
|
.autoComplete_wrapper {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<!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>
|
|
||||||
|
Before Width: | Height: | Size: 546 B |
+16
-55
@@ -69,70 +69,30 @@ function $t(msgId, params={}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const I18n = {
|
function getCookie(cookieName) {
|
||||||
instantiateParameterizedMessages: function(data) {
|
const name = cookieName + "=";
|
||||||
if ( data.__proto__ == Array.prototype ) {
|
let result;
|
||||||
const result = [];
|
decodeURIComponent(document.cookie).split('; ').forEach(val => {
|
||||||
for ( const x of data ) {
|
if (val.indexOf(name) === 0) {
|
||||||
result.push(this.instantiateParameterizedMessages(x));
|
result = val.substring(name.length);
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else if ( data.__proto__ == Object.prototype ) {
|
|
||||||
const msgId = data.msgid;
|
|
||||||
const msgParams = data.params;
|
|
||||||
if ( msgId && msgId.__proto__ == String.prototype && msgParams && msgParams.__proto__ == Object.prototype ) {
|
|
||||||
return $t(msgId, msgParams);
|
|
||||||
} else {
|
|
||||||
const result = {};
|
|
||||||
for ( const p in data ) {
|
|
||||||
result[p] = this.instantiateParameterizedMessages(data[p]);
|
|
||||||
}
|
}
|
||||||
return result;
|
});
|
||||||
}
|
return result;
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function (template, params) {
|
|
||||||
params = this.instantiateParameterizedMessages(params);
|
|
||||||
return mustache.render(template, params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_UI_LANGUAGE = 'en';
|
const DEFAULT_UI_LANGUAGE = 'en';
|
||||||
|
|
||||||
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
|
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
|
||||||
|
|
||||||
// Below function selects the most suitable UI language from the list
|
|
||||||
// of preferred languages in browser preferences and available translations.
|
|
||||||
// Since, unlike Accept-Language header, navigator.languages doesn't contain
|
|
||||||
// qvalues, they are computed using the same algorithm as in Firefox 121
|
|
||||||
function getDefaultUserLanguage() {
|
|
||||||
const mostSuitableLang = { code: DEFAULT_UI_LANGUAGE, score: 0 }
|
|
||||||
const n = navigator.languages.length;
|
|
||||||
for (const lang of uiLanguages ) {
|
|
||||||
const rank = navigator.languages.indexOf(lang.iso_code);
|
|
||||||
if ( rank >= 0 ) {
|
|
||||||
const qvalue = Math.round(10*(1 - rank/n))/10;
|
|
||||||
const score = qvalue * lang.translation_count;
|
|
||||||
if ( score > mostSuitableLang.score ) {
|
|
||||||
mostSuitableLang.code = lang.iso_code;
|
|
||||||
mostSuitableLang.score = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mostSuitableLang.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserLanguage() {
|
function getUserLanguage() {
|
||||||
return new URLSearchParams(window.location.search).get('userlang')
|
return new URLSearchParams(window.location.search).get('userlang')
|
||||||
|| window.localStorage.getItem('userlang')
|
|| getCookie('userlang')
|
||||||
|| getDefaultUserLanguage();
|
|| DEFAULT_UI_LANGUAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUserLanguage(lang, callback) {
|
function setUserLanguage(lang, callback) {
|
||||||
window.localStorage.setItem('userlang', lang);
|
setPermanentGlobalCookie('userlang', lang);
|
||||||
Translations.load(lang);
|
Translations.load(lang);
|
||||||
Translations.whenReady(callback);
|
Translations.whenReady(callback);
|
||||||
}
|
}
|
||||||
@@ -184,8 +144,10 @@ function initUILanguageSelector(activeLanguage, languageChangeCallback) {
|
|||||||
}
|
}
|
||||||
const languageSelector = document.getElementById("ui_language");
|
const languageSelector = document.getElementById("ui_language");
|
||||||
for (const lang of uiLanguages ) {
|
for (const lang of uiLanguages ) {
|
||||||
const is_selected = lang.iso_code == activeLanguage;
|
const lang_name = Object.getOwnPropertyNames(lang)[0];
|
||||||
languageSelector.appendChild(new Option(lang.self_name, lang.iso_code, is_selected, is_selected));
|
const lang_code = lang[lang_name];
|
||||||
|
const is_selected = lang_code == activeLanguage;
|
||||||
|
languageSelector.appendChild(new Option(lang_name, lang_code, is_selected, is_selected));
|
||||||
}
|
}
|
||||||
languageSelector.onchange = languageChangeCallback;
|
languageSelector.onchange = languageChangeCallback;
|
||||||
}
|
}
|
||||||
@@ -194,4 +156,3 @@ window.$t = $t;
|
|||||||
window.getUserLanguage = getUserLanguage;
|
window.getUserLanguage = getUserLanguage;
|
||||||
window.setUserLanguage = setUserLanguage;
|
window.setUserLanguage = setUserLanguage;
|
||||||
window.initUILanguageSelector = initUILanguageSelector;
|
window.initUILanguageSelector = initUILanguageSelector;
|
||||||
window.I18n = I18n;
|
|
||||||
@@ -8,23 +8,8 @@
|
|||||||
"404-page-heading": "পাওয়া যায়নি",
|
"404-page-heading": "পাওয়া যায়নি",
|
||||||
"500-page-title": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
"500-page-title": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||||
"500-page-heading": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
"500-page-heading": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||||
"search-result-book-info": "{{BOOK_TITLE}} থেকে",
|
|
||||||
"word-count": "{{COUNT}}টি শব্দ",
|
|
||||||
"library-button-text": "স্বাগত পাতায় চলুন",
|
"library-button-text": "স্বাগত পাতায় চলুন",
|
||||||
"home-button-text": "'{{BOOK_TITLE}}'-এর প্রধান পাতায় চলুন",
|
"home-button-text": "'{{BOOK_TITLE}}'-এর প্রধান পাতায় চলুন",
|
||||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' অনুসন্ধান করুন",
|
"searchbox-tooltip": "'{{BOOK_TITLE}}' অনুসন্ধান করুন",
|
||||||
"search": "অনুসন্ধান",
|
"search": "অনুসন্ধান"
|
||||||
"welcome-to-kiwix-server": "কিউইক্স সার্ভারে স্বাগতম",
|
|
||||||
"download-links-title": "বই ডাউনলোড করুন",
|
|
||||||
"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": "অন্যান্য"
|
|
||||||
}
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Adriendelucca",
|
|
||||||
"Y-M D"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "brezhoneg",
|
|
||||||
"suggest-full-text-search": "E lec'h emañ \"{{{SEARCH_TERMS}}}\"...",
|
|
||||||
"no-such-book": "N’eus ket eus al levr-mañ: {{BOOK_NAME}}",
|
|
||||||
"no-book-found": "N’eus levr ebet a glot gant an dezverkoù-se",
|
|
||||||
"url-not-found": "N’eo ket bet kavet an URL \"{{url}}\" goulennet war ar servijer-mañ.",
|
|
||||||
"random-article-failure": "Chaous! N’hon eus ket gellet dibab ur pennad dre ziouer evidoc’h :(",
|
|
||||||
"400-page-title": "Reked amwiriek",
|
|
||||||
"400-page-heading": "Reked amwiriek",
|
|
||||||
"404-page-heading": "N'eo ket bet kavet",
|
|
||||||
"500-page-title": "Fazi diabarzh ar servijer",
|
|
||||||
"500-page-heading": "Fazi diabarzh ar servijer",
|
|
||||||
"search-results-page-title": "Klask: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Disoc’hoù <b>{{START}}-{{END}}</b> diwar <b>{{COUNT}}</b> evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"empty-search-results-page-header": "Disoc’h ebet kavet evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"search-result-book-info": "diouzh {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} a c’herioù",
|
|
||||||
"library-button-text": "Mont d’ar bajenn degemer",
|
|
||||||
"home-button-text": "Mont da bajenn degemer \"{{BOOK_TITLE}}\"",
|
|
||||||
"random-page-button-text": "Mont d’ur bajenn dre zegouezh",
|
|
||||||
"searchbox-tooltip": "Klask '{{BOOK_TITLE}}'",
|
|
||||||
"powered-by-kiwix-html": "Lusket gant <a href=\"https://kiwix.org\">Kiwix</a>",
|
|
||||||
"search": "Klask",
|
|
||||||
"book-filtering-all-categories": "An holl rummadoù",
|
|
||||||
"book-filtering-all-languages": "An holl yezhoù",
|
|
||||||
"count-of-matching-books": "{{COUNT}} levr",
|
|
||||||
"download": "Pellgargañ",
|
|
||||||
"direct-download-link-text": "Eeun",
|
|
||||||
"filter-by-tag": "Silañ gant an dikedenn \"{{TAG}}\"",
|
|
||||||
"stop-filtering-by-tag": "Paouez da silañ gant an dikedenn \"{{TAG}}\"",
|
|
||||||
"welcome-to-kiwix-server": "Degemer mat er servijer Kiwix",
|
|
||||||
"download-links-heading": "Liammoù pellgargañ evit <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Pellgargañ al levr",
|
|
||||||
"preview-book": "Rakwelet",
|
|
||||||
"unknown-error": "Fazi dianav"
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Kalakpagh",
|
|
||||||
"Ruky Wunpini"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "Silimiinsili",
|
|
||||||
"suggest-full-text-search": "Gbubi la '{{{SEARCH_TERMS}}}'...",
|
|
||||||
"no-such-book": "Lala buku kani:{{BOOK_NAME}}",
|
|
||||||
"too-many-books": "Buku nima pam ka bɛ daa suhi ({{NB_BOOKS}}) din ni ka tariga nyɛ {{LIMIT}}",
|
|
||||||
"no-book-found": "Buku kani lu zahim a ni piigi yaɣa shɛli",
|
|
||||||
"url-not-found": "URL \"{{url}}\" shɛli bɛ ni daa suhi daa kani n-ti tum tumda ŋɔ.",
|
|
||||||
"suggest-search": "Niŋmi lahabali pali vihigu zaŋ n-ti <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
|
||||||
"random-article-failure": "Oops! Zaɣisiya ni di gahim piigi lahabali :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} nyɛla din suhibu bi niŋ viɛnyɛla zaŋ n-ti lahabali kahili.",
|
|
||||||
"invalid-request": "URL \"{{{url}}}\" shɛli bɛ ni daa suhi ŋɔ nyɛla din bi suhi viɛnyɛla.",
|
|
||||||
"no-value-for-arg": "Dariza shɛli bi ti zaŋ n-ti nangban'kpeeni {{ARGUMENT}}",
|
|
||||||
"no-query": "Yɛlshɛli bi yiina",
|
|
||||||
"raw-entry-not-found": "Ku tooi nya {{DATATYPE}} kpɛbu {{ENTRY}}",
|
|
||||||
"400-page-title": "Suhigu din bi niŋ viɛnyɛla",
|
|
||||||
"400-page-heading": "Suhigu din bi niŋ viɛnyɛla",
|
|
||||||
"404-page-title": "Lahabali kani",
|
|
||||||
"404-page-heading": "Kani",
|
|
||||||
"500-page-title": "Puuni tum tumda chiriŋ",
|
|
||||||
"500-page-heading": "Puuni tum tumda chiriŋ",
|
|
||||||
"500-page-text": "Puuni tum tumda chiriŋ niŋya. Ti niŋ yolitem zaŋ jɛndi li :/",
|
|
||||||
"fulltext-search-unavailable": "Lahabali pali vihigu kani",
|
|
||||||
"search-results-page-title": "Vihima:{{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Chaɣili nima <b>{{START}}-{{END}}</b> of <b>{{COUNT}}</b> for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"empty-search-results-page-header": "Chaɣili daa kani zaŋ n-ti\n <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"search-result-book-info": "yina {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} bachi nima",
|
|
||||||
"library-button-text": "Cham solɔɣu",
|
|
||||||
"home-button-text": "Cham yaɣili maŋmaŋ zaŋ n-ti\n'{{BOOK_TITLE}}'",
|
|
||||||
"random-page-button-text": "Cham gahim piigi yaɣili",
|
|
||||||
"searchbox-tooltip": "Vihima '{{BOOK_TITLE}}'",
|
|
||||||
"confusion-of-tongues": "Buku nima ayi bee gari balli koŋkoba nyɛ din yɛn be vihigu ŋɔ ni ka di ni tooi chɛ ka di laasabu wali.",
|
|
||||||
"welcome-page-overzealous-filter": "Labisibu kani. A ni yu ni a\n<a href=\"{{URL}}\">reset filter</a>?",
|
|
||||||
"powered-by-kiwix-html": "Din niŋ li nyɛ <a href=\"https://kiwix.org\">Kiwix</a>",
|
|
||||||
"search": "Vihima",
|
|
||||||
"book-filtering-all-categories": "Pubu zaa",
|
|
||||||
"book-filtering-all-languages": "Bala zaa",
|
|
||||||
"count-of-matching-books": "{{COUNT}} Buku(nima)",
|
|
||||||
"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": "Deemi buku",
|
|
||||||
"preview-book": "Daŋyuli",
|
|
||||||
"unknown-error": "Chiriŋ din bi tooi baŋ"
|
|
||||||
}
|
|
||||||
@@ -1,67 +1,20 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"IMayBeABitShy",
|
|
||||||
"Justman10000",
|
|
||||||
"Lucas Werkmeister",
|
"Lucas Werkmeister",
|
||||||
"Rofiatmustapha12",
|
|
||||||
"ThisCarthing"
|
"ThisCarthing"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "Deutsch",
|
"name": "Deutsch",
|
||||||
"suggest-full-text-search": "enthält '{{{SEARCH_TERMS}}}'...",
|
|
||||||
"no-such-book": "Buch nicht gefunden: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "Zu viele Bücher angefragt ({{NB_BOOKS}}), die Beschränkung liegt bei {{LIMIT}}",
|
|
||||||
"no-book-found": "Keine Bücher entsprechen den Auswahlkriterien",
|
|
||||||
"url-not-found": "Die angeforderte URL \"{{url}}\" konnte auf diesem Server nicht gefunden werden.",
|
|
||||||
"suggest-search": "Führe eine Volltextsuche nach <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> durch",
|
|
||||||
"random-article-failure": "Hoppla! Konnte keinen zufälligen Artikel auswählen :(",
|
"random-article-failure": "Hoppla! Konnte keinen zufälligen Artikel auswählen :(",
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} ist keine gültige Anfrage für unverarbeiteten Inhalt",
|
|
||||||
"invalid-request": "Die angeforderte URL „{{{url}}}“ ist keine gültige Anfrage.",
|
|
||||||
"no-value-for-arg": "Kein Wert für den Parameter {{ARGUMENT}} gegeben",
|
|
||||||
"no-query": "Keine Suchanfrage gegeben.",
|
|
||||||
"raw-entry-not-found": "Eintrag {{ENTRY}} des Typs {{DATATYPE}} konnte nicht gefunden werden.",
|
|
||||||
"400-page-title": "Ungültige Anfrage",
|
"400-page-title": "Ungültige Anfrage",
|
||||||
"400-page-heading": "Ungültige Anfrage",
|
"400-page-heading": "Ungültige Anfrage",
|
||||||
"404-page-title": "Inhalt nicht gefunden",
|
"404-page-title": "Inhalt nicht gefunden",
|
||||||
"404-page-heading": "Nicht gefunden",
|
"404-page-heading": "Nicht gefunden",
|
||||||
"500-page-title": "Interner Server-Fehler",
|
"500-page-title": "Interner Server-Fehler",
|
||||||
"500-page-heading": "Interner Server-Fehler",
|
"500-page-heading": "Interner Server-Fehler",
|
||||||
"500-page-text": "Es ist ein interner Serverfehler aufgetreten. Das tut uns leid :/",
|
|
||||||
"fulltext-search-unavailable": "Die Volltestsuche steht nicht zur Verfügung.",
|
|
||||||
"no-search-results": "Die Volltextsuche ist für diesen Inhalt nicht verfügbar.",
|
|
||||||
"search-results-page-title": "Suche: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Ergebnisse <b>{{START}}-{{END}}</b> von <b>{{COUNT}}</b> für <b>„{{{SEARCH_PATTERN}}}“</b>",
|
|
||||||
"empty-search-results-page-header": "Für <b>„{{{SEARCH_PATTERN}}}“</b> wurden keine Ergebnisse gefunden.",
|
|
||||||
"search-result-book-info": "von {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} Wörter",
|
|
||||||
"library-button-text": "Zur Willkommensseite gehen",
|
"library-button-text": "Zur Willkommensseite gehen",
|
||||||
"home-button-text": "Zur Hauptseite von '{{BOOK_TITLE}}' gehen",
|
"home-button-text": "Zur Hauptseite von '{{BOOK_TITLE}}' gehen",
|
||||||
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
|
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
|
||||||
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen",
|
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen"
|
||||||
"confusion-of-tongues": "Zwei oder mehr Bücher unterschiedlicher Sprachen werden durchsucht, was zu unübersichtlichen Ergebnissen führen kann.",
|
|
||||||
"welcome-page-overzealous-filter": "Keine Ergebnisse gefunden. Möchten Sie den <a href=\"{{URL}}\">Filter zurücksetzen</a>?",
|
|
||||||
"powered-by-kiwix-html": "Angetrieben durch <a href=\"https://kiwix.org\">Kiwix</a>",
|
|
||||||
"search": "Suchen",
|
|
||||||
"book-filtering-all-categories": "Alle Kategorien",
|
|
||||||
"book-filtering-all-languages": "Alle Sprachen",
|
|
||||||
"count-of-matching-books": "{{COUNT}} Bücher",
|
|
||||||
"download": "Herunterladen",
|
|
||||||
"direct-download-link-text": "Direkt",
|
|
||||||
"direct-download-alt-text": "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": "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",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Alhaji Yakubu",
|
"Alhaji Yakubu"
|
||||||
"Amire80"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"welcome-page-overzealous-filter": "Duoro kyebe. <a href=\"{{URL}}\">E na boɔra ka fo</a>?",
|
"welcome-page-overzealous-filter": "Duoro kyebe. E na boɔra ka fo <a href=\"?lang=\"></a>",
|
||||||
"search": "Bo",
|
"search": "Bo",
|
||||||
"book-filtering-all-categories": "Zagre zaa",
|
"book-filtering-all-categories": "Zagre zaa",
|
||||||
"book-filtering-all-languages": "Kɔkɔrɛɛ zaa",
|
"book-filtering-all-languages": "Kɔkɔrɛɛ zaa",
|
||||||
@@ -16,6 +15,7 @@
|
|||||||
"hash-download-alt-text": "Tage bonmannaa",
|
"hash-download-alt-text": "Tage bonmannaa",
|
||||||
"magnet-link-text": "Kurimaraa sobie",
|
"magnet-link-text": "Kurimaraa sobie",
|
||||||
"magnet-alt-text": "Tage kurimaraa",
|
"magnet-alt-text": "Tage kurimaraa",
|
||||||
|
"library-opds-feed": "Gamadie OPDS diibu",
|
||||||
"filter-by-tag": "Guy yi kpuli {{TAG}}",
|
"filter-by-tag": "Guy yi kpuli {{TAG}}",
|
||||||
"stop-filtering-by-tag": "Bare gyɛɛbo kpuli {{TAG}}"
|
"stop-filtering-by-tag": "Bare gyɛɛbo kpuli {{TAG}}"
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kelson",
|
"Norhorn"
|
||||||
"Norhorn",
|
|
||||||
"Ανώνυμος Βικιπαιδιστής"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "Αγγλικά",
|
"welcome-page-overzealous-filter": "Κανένα αποτέλεσμα. Θέλετε να <a href=\"?lang=\">επαναφέρετε το φίλτρο</a>;",
|
||||||
"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>",
|
"powered-by-kiwix-html": "Με την υποστήριξη by <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||||
"search": "Αναζήτηση",
|
"search": "Αναζήτηση",
|
||||||
"book-filtering-all-categories": "Όλες οι κατηγορίες",
|
"book-filtering-all-categories": "Όλες οι κατηγορίες",
|
||||||
@@ -18,14 +14,8 @@
|
|||||||
"direct-download-link-text": "Απευθείας",
|
"direct-download-link-text": "Απευθείας",
|
||||||
"direct-download-alt-text": "άμεση λήψη",
|
"direct-download-alt-text": "άμεση λήψη",
|
||||||
"hash-download-alt-text": "λήψη αναγνωριστικού",
|
"hash-download-alt-text": "λήψη αναγνωριστικού",
|
||||||
"magnet-alt-text": "λήψη μαγνήτη",
|
|
||||||
"torrent-download-link-text": "Αρχείο torrent",
|
"torrent-download-link-text": "Αρχείο torrent",
|
||||||
"torrent-download-alt-text": "λήψη torrent",
|
"torrent-download-alt-text": "λήψη torrent",
|
||||||
"filter-by-tag": "Φίλτρο ανά ετικέτα \"{{TAG}}\"",
|
"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": "Άγνωστο σφάλμα"
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
, "suggest-search" : "Make a full text search for <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>"
|
, "suggest-search" : "Make a full text search for <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>"
|
||||||
, "random-article-failure" : "Oops! Failed to pick a random article :("
|
, "random-article-failure" : "Oops! Failed to pick a random article :("
|
||||||
, "invalid-raw-data-type" : "{{DATATYPE}} is not a valid request for raw content."
|
, "invalid-raw-data-type" : "{{DATATYPE}} is not a valid request for raw content."
|
||||||
, "invalid-request" : "The requested URL \"{{{url}}}\" is not a valid request."
|
|
||||||
, "no-value-for-arg": "No value provided for argument {{ARGUMENT}}"
|
, "no-value-for-arg": "No value provided for argument {{ARGUMENT}}"
|
||||||
, "no-query" : "No query provided."
|
, "no-query" : "No query provided."
|
||||||
, "raw-entry-not-found" : "Cannot find {{DATATYPE}} entry {{ENTRY}}"
|
, "raw-entry-not-found" : "Cannot find {{DATATYPE}} entry {{ENTRY}}"
|
||||||
@@ -22,14 +21,8 @@
|
|||||||
, "404-page-heading" : "Not Found"
|
, "404-page-heading" : "Not Found"
|
||||||
, "500-page-title" : "Internal Server Error"
|
, "500-page-title" : "Internal Server Error"
|
||||||
, "500-page-heading" : "Internal Server Error"
|
, "500-page-heading" : "Internal Server Error"
|
||||||
, "500-page-text": "An internal server error occured. We are sorry about that :/"
|
|
||||||
, "fulltext-search-unavailable" : "Fulltext search unavailable"
|
, "fulltext-search-unavailable" : "Fulltext search unavailable"
|
||||||
, "no-search-results": "The fulltext search engine is not available for this content."
|
, "no-search-results": "The fulltext search engine is not available for this content."
|
||||||
, "search-results-page-title": "Search: {{SEARCH_PATTERN}}"
|
|
||||||
, "search-results-page-header": "Results <b>{{START}}-{{END}}</b> of <b>{{COUNT}}</b> for <b>\"{{{SEARCH_PATTERN}}}\"</b>"
|
|
||||||
, "empty-search-results-page-header": "No results were found for <b>\"{{{SEARCH_PATTERN}}}\"</b>"
|
|
||||||
, "search-result-book-info": "from {{BOOK_TITLE}}"
|
|
||||||
, "word-count": "{{COUNT}} words"
|
|
||||||
, "library-button-text": "Go to welcome page"
|
, "library-button-text": "Go to welcome page"
|
||||||
, "home-button-text": "Go to the main page of '{{BOOK_TITLE}}'"
|
, "home-button-text": "Go to the main page of '{{BOOK_TITLE}}'"
|
||||||
, "random-page-button-text": "Go to a randomly selected page"
|
, "random-page-button-text": "Go to a randomly selected page"
|
||||||
@@ -43,39 +36,19 @@
|
|||||||
, "count-of-matching-books": "{{COUNT}} book(s)"
|
, "count-of-matching-books": "{{COUNT}} book(s)"
|
||||||
, "download": "Download"
|
, "download": "Download"
|
||||||
, "direct-download-link-text": "Direct"
|
, "direct-download-link-text": "Direct"
|
||||||
, "direct-download-alt-text": "Download directly via HTTP(S)"
|
, "direct-download-alt-text": "direct download"
|
||||||
, "hash-download-link-text": "SHA-256 checksum"
|
, "hash-download-link-text": "Sha256 hash"
|
||||||
, "hash-download-alt-text": "Display SHA-256 file checksum"
|
, "hash-download-alt-text": "download hash"
|
||||||
, "magnet-link-text": "Magnet link"
|
, "magnet-link-text": "Magnet link"
|
||||||
, "magnet-alt-text": "Download via Magnet link"
|
, "magnet-alt-text": "download magnet"
|
||||||
, "torrent-download-link-text": "BitTorrent"
|
, "torrent-download-link-text": "Torrent file"
|
||||||
, "torrent-download-alt-text": "Download via BitTorrent"
|
, "torrent-download-alt-text": "download torrent"
|
||||||
, "library-opds-feed-all-entries": "Library OPDS Feed - All entries"
|
, "library-opds-feed-all-entries": "Library OPDS Feed - All entries"
|
||||||
, "filter-by-tag": "Filter by tag \"{{{TAG}}}\""
|
, "filter-by-tag": "Filter by tag \"{{TAG}}\""
|
||||||
, "stop-filtering-by-tag": "Stop filtering 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}}"
|
, "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"
|
, "welcome-to-kiwix-server": "Welcome to Kiwix Server"
|
||||||
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
|
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
|
||||||
, "download-links-title": "Download book"
|
, "download-links-title": "Download book"
|
||||||
, "preview-book": "Preview"
|
, "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"
|
|
||||||
}
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"AlexanderFF",
|
|
||||||
"Fitoschido",
|
|
||||||
"Ovruni",
|
|
||||||
"SpikeShroom",
|
|
||||||
"Vis4valentine"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "español",
|
|
||||||
"suggest-full-text-search": "que contenga \"{{{SEARCH_TERMS}}}\"...",
|
|
||||||
"no-such-book": "No existe el libro: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "Demasiadas solicitudes de libros ({{NB_BOOKS}}) donde el límite es {{LIMIT}}",
|
|
||||||
"no-book-found": "Ningún libro coincide con los criterios de selección.",
|
|
||||||
"url-not-found": "La URL solicitada \"{{url}}\" no se encontró en este servidor.",
|
|
||||||
"suggest-search": "Haga una búsqueda de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
|
||||||
"random-article-failure": "¡Ups! Error al elegir un artículo aleatorio :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} no es una solicitud válida de contenido en crudo.",
|
|
||||||
"no-value-for-arg": "No se ha proporcionado ningún valor para el argumento {{ARGUMENT}}",
|
|
||||||
"no-query": "No se ha proporcionado ninguna consulta.",
|
|
||||||
"raw-entry-not-found": "No se puede encontrar la entrada {{DATATYPE}} {{ENTRY}}",
|
|
||||||
"400-page-title": "Solicitud inválida",
|
|
||||||
"400-page-heading": "Solicitud inválida",
|
|
||||||
"404-page-title": "Contenido no encontrado",
|
|
||||||
"404-page-heading": "No encontrado",
|
|
||||||
"500-page-title": "Error interno del servidor",
|
|
||||||
"500-page-heading": "Error interno del servidor",
|
|
||||||
"fulltext-search-unavailable": "Búsqueda de texto completo no disponible",
|
|
||||||
"no-search-results": "El motor de búsqueda de texto completo no está disponible para este contenido.",
|
|
||||||
"library-button-text": "Ir a la página de bienvenida",
|
|
||||||
"home-button-text": "Ir a la página principal de '{{BOOK_TITLE}}'",
|
|
||||||
"random-page-button-text": "Ir a una página seleccionada al azar",
|
|
||||||
"searchbox-tooltip": "Buscar '{{BOOK_TITLE}}'",
|
|
||||||
"confusion-of-tongues": "Dos o más libros en diferentes idiomas participarían en la búsqueda, lo que puede llevar a resultados confusos.",
|
|
||||||
"welcome-page-overzealous-filter": "Sin resultados. ¿Quieres <a href=\"{{URL}}\">restablecer el filtro</a> ?",
|
|
||||||
"powered-by-kiwix-html": "Desarrollado por <a href=\"https://kiwix.org\">Kiwix</a>",
|
|
||||||
"search": "Buscar",
|
|
||||||
"book-filtering-all-categories": "Todas las categorías",
|
|
||||||
"book-filtering-all-languages": "Todos los idiomas",
|
|
||||||
"count-of-matching-books": "{{COUNT}} libro(s)",
|
|
||||||
"download": "Descargar",
|
|
||||||
"direct-download-link-text": "Directamente",
|
|
||||||
"direct-download-alt-text": "descarga directa",
|
|
||||||
"hash-download-link-text": "hash sha256",
|
|
||||||
"hash-download-alt-text": "descargar hash",
|
|
||||||
"magnet-link-text": "Enlace magnético",
|
|
||||||
"magnet-alt-text": "Descargar link magnético",
|
|
||||||
"torrent-download-link-text": "BitTorrent",
|
|
||||||
"torrent-download-alt-text": "Descargar a través de BitTorrent",
|
|
||||||
"filter-by-tag": "Filtrar por etiqueta \"{{TAG}}\"",
|
|
||||||
"stop-filtering-by-tag": "Dejar de filtrar por etiqueta \"{{TAG}}\"",
|
|
||||||
"library-opds-feed-parameterised": "Feed OPDS de la biblioteca: entradas que coinciden con {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nEtiqueta: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Bienvenido al servidor Kiwix",
|
|
||||||
"download-links-heading": "Enlaces de descarga para <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Descargar libro",
|
|
||||||
"preview-book": "Previsualizar",
|
|
||||||
"unknown-error": "Error desconocido"
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"MITO",
|
|
||||||
"Nike",
|
|
||||||
"Pyscowicz"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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ö",
|
|
||||||
"404-page-title": "Sisältöä ei löytynyt",
|
|
||||||
"404-page-heading": "Ei löytynyt",
|
|
||||||
"500-page-title": "Sisäinen palvelinvirhe",
|
|
||||||
"500-page-heading": "Sisäinen palvelinvirhe",
|
|
||||||
"word-count": "{{COUNT}} sanaa",
|
|
||||||
"library-button-text": "Siirry tervetulosivulle",
|
|
||||||
"home-button-text": "Siirry kirjan '{{BOOK_TITLE}}' etusivulle",
|
|
||||||
"random-page-button-text": "Siirry satunnaiselle sivulle",
|
|
||||||
"searchbox-tooltip": "Hae '{{BOOK_TITLE}}'",
|
|
||||||
"search": "Hae",
|
|
||||||
"book-filtering-all-categories": "Kaikki luokat",
|
|
||||||
"book-filtering-all-languages": "Kaikki kielet",
|
|
||||||
"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",
|
|
||||||
"unknown-error": "Tuntematon virhe"
|
|
||||||
}
|
|
||||||
+12
-43
@@ -1,20 +1,12 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Adriendelucca",
|
|
||||||
"Benoit74",
|
|
||||||
"Gomoko",
|
"Gomoko",
|
||||||
"Goombiis",
|
|
||||||
"Melimeli",
|
|
||||||
"Stephane",
|
|
||||||
"Thibaut120094",
|
"Thibaut120094",
|
||||||
"Urhixidur",
|
"Verdy p"
|
||||||
"Verdy p",
|
|
||||||
"Vikoula5",
|
|
||||||
"Wladek92"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "Français",
|
"name": "français",
|
||||||
"suggest-full-text-search": "contenant « {{{SEARCH_TERMS}}} »...",
|
"suggest-full-text-search": "contenant « {{{SEARCH_TERMS}}} »...",
|
||||||
"no-such-book": "Aucun livre avec ce nom : {{BOOK_NAME}}",
|
"no-such-book": "Aucun livre avec ce nom : {{BOOK_NAME}}",
|
||||||
"too-many-books": "Trop de livres demandés ({{NB_BOOKS}}) alors que la limite est de {{LIMIT}}",
|
"too-many-books": "Trop de livres demandés ({{NB_BOOKS}}) alors que la limite est de {{LIMIT}}",
|
||||||
@@ -23,7 +15,6 @@
|
|||||||
"suggest-search": "Faire une recherche en texte intégral de « <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> »",
|
"suggest-search": "Faire une recherche en texte intégral de « <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> »",
|
||||||
"random-article-failure": "Oups ! Échec de sélection d’un article aléatoire :(",
|
"random-article-failure": "Oups ! Échec de sélection d’un article aléatoire :(",
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} n’est pas une requête valide pour du contenu brut.",
|
"invalid-raw-data-type": "{{DATATYPE}} n’est pas une requête valide pour du contenu brut.",
|
||||||
"invalid-request": "L'URL demandée \"{{{url}}}\" n'est pas une requête valide.",
|
|
||||||
"no-value-for-arg": "Aucune valeur fournie pour l’argument {{ARGUMENT}}",
|
"no-value-for-arg": "Aucune valeur fournie pour l’argument {{ARGUMENT}}",
|
||||||
"no-query": "Aucune requête fournie.",
|
"no-query": "Aucune requête fournie.",
|
||||||
"raw-entry-not-found": "Impossible de trouver l’entrée « {{ENTRY}} » de type « {{DATATYPE}} »",
|
"raw-entry-not-found": "Impossible de trouver l’entrée « {{ENTRY}} » de type « {{DATATYPE}} »",
|
||||||
@@ -33,20 +24,14 @@
|
|||||||
"404-page-heading": "Non trouvé",
|
"404-page-heading": "Non trouvé",
|
||||||
"500-page-title": "Erreur interne du serveur",
|
"500-page-title": "Erreur interne du serveur",
|
||||||
"500-page-heading": "Erreur interne du serveur",
|
"500-page-heading": "Erreur interne du serveur",
|
||||||
"500-page-text": "Une erreur de serveur interne s'est produite. Nous en sommes désolés :/",
|
|
||||||
"fulltext-search-unavailable": "Recherche en texte intégral non disponible",
|
"fulltext-search-unavailable": "Recherche en texte intégral non disponible",
|
||||||
"no-search-results": "Le moteur de recherche en texte intégral n’est pas disponible pour ce contenu.",
|
"no-search-results": "Le moteur de recherche en texte intégral n’est pas disponible pour ce contenu.",
|
||||||
"search-results-page-title": "Rechercher : {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Résultats <b>{{START}}-{{END}}</b> sur<b> {{COUNT}}</b> pour <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"empty-search-results-page-header": "Aucun résultat n’a été trouvé pour <b>« {{{SEARCH_PATTERN}}} »</b>",
|
|
||||||
"search-result-book-info": "à partir de {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} mots",
|
|
||||||
"library-button-text": "Aller à la page de bienvenue",
|
"library-button-text": "Aller à la page de bienvenue",
|
||||||
"home-button-text": "Aller à la page principale de « {{BOOK_TITLE}} »",
|
"home-button-text": "Aller à la page principale de « {{BOOK_TITLE}} »",
|
||||||
"random-page-button-text": "Aller à une page sélectionnée aléatoirement",
|
"random-page-button-text": "Aller à une page sélectionnée aléatoirement",
|
||||||
"searchbox-tooltip": "Rechercher « {{BOOK_TITLE}} »",
|
"searchbox-tooltip": "Rechercher « {{BOOK_TITLE}} »",
|
||||||
"confusion-of-tongues": "Deux livres ou plus dans des langues différentes participeraient à la recherche, ce qui pourrait conduire à des résultats confus.",
|
"confusion-of-tongues": "Deux livres ou plus dans des langues différentes participeraient à la recherche, ce qui pourrait conduire à des résultats confus.",
|
||||||
"welcome-page-overzealous-filter": "Aucun résultat. Souhaitez-vous <a href=\"{{URL}}\">réinitialiser le filtre</a> ?",
|
"welcome-page-overzealous-filter": "Aucun résultat. Souhaitez-vous <a href=\"?lang=\">réinitialiser le filtre</a> ?",
|
||||||
"powered-by-kiwix-html": "Propulsé par <a href=\"https://kiwix.org/\">Kiwix</a>",
|
"powered-by-kiwix-html": "Propulsé par <a href=\"https://kiwix.org/\">Kiwix</a>",
|
||||||
"search": "Rechercher",
|
"search": "Rechercher",
|
||||||
"book-filtering-all-categories": "Toutes les catégories",
|
"book-filtering-all-categories": "Toutes les catégories",
|
||||||
@@ -54,30 +39,14 @@
|
|||||||
"count-of-matching-books": "{{COUNT}} livre(s)",
|
"count-of-matching-books": "{{COUNT}} livre(s)",
|
||||||
"download": "Télécharger",
|
"download": "Télécharger",
|
||||||
"direct-download-link-text": "Direct",
|
"direct-download-link-text": "Direct",
|
||||||
"direct-download-alt-text": "Télécharger directement via HTTP(S)",
|
"direct-download-alt-text": "téléchargement direct",
|
||||||
"hash-download-link-text": "Hachage SHA-256",
|
"hash-download-link-text": "Hachage sha256",
|
||||||
"hash-download-alt-text": "Affiche le hachage SHA-256 du fichier",
|
"hash-download-alt-text": "télécharger le hachage",
|
||||||
"magnet-link-text": "Lien Magnet",
|
"magnet-link-text": "Lien Magnet",
|
||||||
"magnet-alt-text": "Télécharger via le lien Magnet",
|
"magnet-alt-text": "télécharger le lien Magnet",
|
||||||
"torrent-download-link-text": "BitTorrent",
|
"torrent-download-link-text": "Fichier torrent",
|
||||||
"torrent-download-alt-text": "Télécharger via BitTorrent",
|
"torrent-download-alt-text": "télécharger le torrent",
|
||||||
"library-opds-feed-all-entries": "Flux OPDS de la bibliothèque – Toutes les entrées",
|
"library-opds-feed": "Flux OPDS de la bibliothèque",
|
||||||
"filter-by-tag": "Filtrer par le tag \"{{{TAG}}}\"",
|
"filter-by-tag": "Filtrer par la balise « {{TAG}} »",
|
||||||
"stop-filtering-by-tag": "Arrêter de filtrer par le tag \"{{{TAG}}}\"",
|
"stop-filtering-by-tag": "Arrêter le filtrage par la balise « {{TAG}} »"
|
||||||
"library-opds-feed-parameterised": "Flux OPDS de la bibliothèque – Entrées correspondant à {{#LANG}} :\n ▪ Langue : {{LANG}} {{/LANG}}{{#CATEGORY}}\n ▪ Catégorie : {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n ▪ Étiquette : {{TAG}} {{/TAG}}{{#Q}}\n ▪ Requête : {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Bienvenue sur le Serveur Kiwix",
|
|
||||||
"download-links-heading": "Liens de téléchargement pour <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Télécharger le livre",
|
|
||||||
"preview-book": "Aperçu",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Abelidokoo",
|
|
||||||
"El-hussain14",
|
|
||||||
"Rofiatmustapha12",
|
|
||||||
"Smshika",
|
|
||||||
"Yusuf Sa'adu"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "Turanci",
|
|
||||||
"suggest-full-text-search": "dauke da ''{{{SEARCH_TERMS}}}''...",
|
|
||||||
"no-such-book": "Babu irin wannan littafin: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "An nemi littattafai da yawa ({{NB_BOOKS}}) inda iyaka shine {{LIMIT}}",
|
|
||||||
"no-book-found": "Babu wani littafi da ya dace da ma'aunin zaɓi",
|
|
||||||
"url-not-found": "Ba a sami URL ɗin da ake nema \"{{url}}\" akan wannan sabar ba.",
|
|
||||||
"suggest-search": "Yi cikakken bincike na rubutu don <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
|
||||||
"random-article-failure": "Kash! An kasa ɗaukar labarin bazuwar :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} ba ingantaccen buƙatun ɗanyen abun ciki bane.",
|
|
||||||
"invalid-request": "URL ɗin da ake nema \"{{{url}}}\" ba buƙatu mai inganci bane.",
|
|
||||||
"no-value-for-arg": "Babu ƙima da aka bayar don hujja {{ARGUMENT}}",
|
|
||||||
"no-query": "Ba a bayar da tambaya ba.",
|
|
||||||
"raw-entry-not-found": "Ba a iya samun shigarwar {{DATATYPE}} {{ENTRY}}",
|
|
||||||
"400-page-title": "nema mara inganci",
|
|
||||||
"400-page-heading": "nema mara inganci",
|
|
||||||
"404-page-title": "Ba a samo abun ciki ba",
|
|
||||||
"404-page-heading": "Ba a Samu ba",
|
|
||||||
"500-page-title": "Kuskuren na Cikin Saba",
|
|
||||||
"500-page-heading": "Kuskuren na Cikin Saba",
|
|
||||||
"500-page-text": "An sami kuskuren uwar garken ciki. Munyi nadama akan hakan :/",
|
|
||||||
"fulltext-search-unavailable": "Babu binciken cikakken rubutu",
|
|
||||||
"no-search-results": "Babu injin binciken cikakken rubutu don wannan abun ciki.",
|
|
||||||
"search-results-page-title": "Bincika: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Sakamako <b>{{START}}-{{END}}</b> na <b>{{COUNT}}</b> na <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"empty-search-results-page-header": "Ba a sami sakamakon <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"search-result-book-info": "daga {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} kalmomi",
|
|
||||||
"library-button-text": "Je zuwa shafin maraba",
|
|
||||||
"home-button-text": "Jeka babban shafin '{{BOOK_TITLE}}'",
|
|
||||||
"random-page-button-text": "Je zuwa shafin da aka zaɓa ba da gangan ba",
|
|
||||||
"searchbox-tooltip": "Bincika '{{BOOK_TITLE}}'",
|
|
||||||
"confusion-of-tongues": "Littattafai biyu ko fiye a cikin harsuna daban-daban za su shiga cikin bincike, wanda zai iya haifar da sakamako mai ruɗani.",
|
|
||||||
"welcome-page-overzealous-filter": "Babu sakamako. Kuna so a <a href=\"{{URL}}\">sake saita tace</a>?",
|
|
||||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> ne ke ƙarfafa shi",
|
|
||||||
"search": "Nema",
|
|
||||||
"book-filtering-all-categories": "Dukkanin nau'o'in",
|
|
||||||
"book-filtering-all-languages": "Duka harsuna",
|
|
||||||
"count-of-matching-books": "{{COUNT}} littafi(s)",
|
|
||||||
"download": "Sauke",
|
|
||||||
"direct-download-link-text": "Kai tsaye",
|
|
||||||
"direct-download-alt-text": "saukewa kai tsaye",
|
|
||||||
"hash-download-link-text": "Sha256 hash",
|
|
||||||
"hash-download-alt-text": "sauke hash",
|
|
||||||
"magnet-link-text": "Magnet link",
|
|
||||||
"magnet-alt-text": "Magnet ɗin saukewa",
|
|
||||||
"torrent-download-link-text": "Torrent fayil",
|
|
||||||
"torrent-download-alt-text": "download torrent",
|
|
||||||
"library-opds-feed-all-entries": "Ciyarwar OPDS Library - Duk shigarwar",
|
|
||||||
"filter-by-tag": "Tace da alamar \"{{TAG}}\"",
|
|
||||||
"stop-filtering-by-tag": "Dakatar da tacewa ta hanyar \"{{TAG}}\"",
|
|
||||||
"library-opds-feed-parameterised": " OPDS ciyar wa OPDS- entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Barka da zowa manhajar Kiwix",
|
|
||||||
"download-links-heading": "Bring out ways through which people can join hands together .{{BOOK_TITLE}}",
|
|
||||||
"download-links-title": "Sauke littafin",
|
|
||||||
"preview-book": "Dubawa",
|
|
||||||
"unknown-error": "Kuskuren da ba a sani ba"
|
|
||||||
}
|
|
||||||
+10
-35
@@ -14,7 +14,6 @@
|
|||||||
"suggest-search": "לעשות חיפוש טקסט מלא עבור <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
"suggest-search": "לעשות חיפוש טקסט מלא עבור <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||||
"random-article-failure": "אוי! לא עבדה בחירת ערך אקראי :(",
|
"random-article-failure": "אוי! לא עבדה בחירת ערך אקראי :(",
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} הוא לא בקשה תקינה של תוכן גולמי.",
|
"invalid-raw-data-type": "{{DATATYPE}} הוא לא בקשה תקינה של תוכן גולמי.",
|
||||||
"invalid-request": "הכתובת המבוקשת \"{{{url}}}\" אינה בקשה תקינה.",
|
|
||||||
"no-value-for-arg": "לא סופק ערך לארגומנט {{ARGUMENT}}",
|
"no-value-for-arg": "לא סופק ערך לארגומנט {{ARGUMENT}}",
|
||||||
"no-query": "לא סופקה שאילתה.",
|
"no-query": "לא סופקה שאילתה.",
|
||||||
"raw-entry-not-found": "לא ניתן למצוא את רשומת ה־{{DATATYPE}} בשם {{ENTRY}}",
|
"raw-entry-not-found": "לא ניתן למצוא את רשומת ה־{{DATATYPE}} בשם {{ENTRY}}",
|
||||||
@@ -24,20 +23,14 @@
|
|||||||
"404-page-heading": "לא נמצא",
|
"404-page-heading": "לא נמצא",
|
||||||
"500-page-title": "שגיאת שרת פנימית",
|
"500-page-title": "שגיאת שרת פנימית",
|
||||||
"500-page-heading": "שגיאת שרת פנימית",
|
"500-page-heading": "שגיאת שרת פנימית",
|
||||||
"500-page-text": "אירעה שגיאת שרת פנימית. אנחנו מצטערים על זה :/",
|
|
||||||
"fulltext-search-unavailable": "חיפוש בטקסט מלא אינו זמין",
|
"fulltext-search-unavailable": "חיפוש בטקסט מלא אינו זמין",
|
||||||
"no-search-results": "מנוע החיפוש בטקסט מלא אינו זמין עבור התוכן הזה.",
|
"no-search-results": "מנוע החיפוש בטקסט מלא אינו זמין עבור התוכן הזה.",
|
||||||
"search-results-page-title": "חיפוש: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "תוצאות <b>{{START}} עד {{END}}</b> מתוך <b>{{COUNT}}</b> עבור <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"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}}\"",
|
"home-button-text": "מעבר לדף הראשי של \"{{BOOK_TITLE}}\"",
|
||||||
"random-page-button-text": "מעבר לדף שנבחר אקראית",
|
"random-page-button-text": "מעבר לדף שנבחר אקראית",
|
||||||
"searchbox-tooltip": "חיפוש \"{{BOOK_TITLE}}\"",
|
"searchbox-tooltip": "חיפוש \"{{BOOK_TITLE}}\"",
|
||||||
"confusion-of-tongues": "שני ספרים או יותר בשפות שונות ישתתפו בחיפוש, מה שעלול להוביל לתוצאות מבלבלות.",
|
"confusion-of-tongues": "שני ספרים או יותר בשפות שונות ישתתפו בחיפוש, מה שעלול להוביל לתוצאות מבלבלות.",
|
||||||
"welcome-page-overzealous-filter": "אין תוצאות. האם <a href=\"{{URL}}\">לאפס את המסנן</a>?",
|
"welcome-page-overzealous-filter": "אין תוצאות. האם <a href=\"?lang=\">לאפס את המסנן</a>?",
|
||||||
"powered-by-kiwix-html": "מופעל על־ידי <a href=\"https://kiwix.org\">Kiwix</a>",
|
"powered-by-kiwix-html": "מופעל על־ידי <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||||
"search": "חיפוש",
|
"search": "חיפוש",
|
||||||
"book-filtering-all-categories": "כל הקטגוריות",
|
"book-filtering-all-categories": "כל הקטגוריות",
|
||||||
@@ -45,32 +38,14 @@
|
|||||||
"count-of-matching-books": "{{COUNT}} ספרים",
|
"count-of-matching-books": "{{COUNT}} ספרים",
|
||||||
"download": "הורדה",
|
"download": "הורדה",
|
||||||
"direct-download-link-text": "ישירה",
|
"direct-download-link-text": "ישירה",
|
||||||
"direct-download-alt-text": "הורדה ישירה דרך HTTP(S)",
|
"direct-download-alt-text": "הורדה ישירה",
|
||||||
"hash-download-link-text": "סיכום ביקורת Sha256",
|
"hash-download-link-text": "גיבוב Sha256",
|
||||||
"hash-download-alt-text": "הצגת סיכום ביקורת SHA-256",
|
"hash-download-alt-text": "הורדת גיבוב",
|
||||||
"magnet-link-text": "קישור Magnet",
|
"magnet-link-text": "קישור Magnet",
|
||||||
"magnet-alt-text": "הורדה באמצעות קישור magnet",
|
"magnet-alt-text": "הורדת magnet",
|
||||||
"torrent-download-link-text": "ביטורנט",
|
"torrent-download-link-text": "קובץ טורנט",
|
||||||
"torrent-download-alt-text": "הורדה באמצעות ביטורנט",
|
"torrent-download-alt-text": "הורדת טורנט",
|
||||||
"library-opds-feed-all-entries": "הזנת ספריית OPDS - כל הרשומות",
|
"library-opds-feed": "הזנת OPDS של ספרייה",
|
||||||
"filter-by-tag": "לסנן לפי התג \"{{{TAG}}}\"",
|
"filter-by-tag": "סינון לפי התג \"{{TAG}}\"",
|
||||||
"stop-filtering-by-tag": "להפסיק סינון לפי התג \"{{{TAG}}}\"",
|
"stop-filtering-by-tag": "להפסיק סינון לפי התג \"{{TAG}}\""
|
||||||
"library-opds-feed-parameterised": "הזנת ספריית OPDS - רשומות שתואמות ל{{#LANG}}\nשפה: {{LANG}} {{/LANG}}{{#CATEGORY}}\nקטגוריה: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nתג: {{TAG}} {{/TAG}}{{#Q}}\nשאילתה: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "ברוך בואך לשרת קיוויקס",
|
|
||||||
"download-links-heading": "הורדת קישורים עבור <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "הורדת ספר",
|
|
||||||
"preview-book": "תצוגה מקדימה",
|
|
||||||
"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": "אחר"
|
|
||||||
}
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Abijeet Patro",
|
|
||||||
"Juuz0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "हिन्दी",
|
|
||||||
"suggest-full-text-search": "जिसमें '{{{SEARCH_TERMS}}}' शामिल है...",
|
|
||||||
"no-such-book": "ऐसी कोई किताब नहीं: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "बहुत सारी पुस्तकों का अनुरोध किया गया है ({{NB_BOOKS}}) जहां सीमा {{LIMIT}} है",
|
|
||||||
"no-book-found": "कोई भी पुस्तक चयन मानदंड से मेल नहीं खाती",
|
|
||||||
"url-not-found": "अनुरोधित URL \"{{url}}\" इस सर्वर पर नहीं मिला।",
|
|
||||||
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> के लिए पूर्ण पाठ खोज करें",
|
|
||||||
"random-article-failure": "उफ़! एक यादृच्छिक लेख चुनने में विफल :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} कच्ची सामग्री के लिए वैध अनुरोध नहीं है।",
|
|
||||||
"no-value-for-arg": "तर्क के लिए कोई मूल्य प्रदान नहीं किया गया {{ARGUMENT}}",
|
|
||||||
"no-query": "कोई प्रश्न नहीं दिया गया.",
|
|
||||||
"raw-entry-not-found": "{{DATATYPE}} प्रविष्टि {{ENTRY}} नहीं मिल सकी",
|
|
||||||
"400-page-title": "अवैध आवेदन",
|
|
||||||
"400-page-heading": "अवैध आवेदन",
|
|
||||||
"404-page-title": "सामग्री नहीं मिली",
|
|
||||||
"404-page-heading": "नहीं मिला",
|
|
||||||
"500-page-title": "आंतरिक सर्वर त्रुटि",
|
|
||||||
"500-page-heading": "आंतरिक सर्वर त्रुटि",
|
|
||||||
"fulltext-search-unavailable": "पूर्णपाठ खोज अनुपलब्ध",
|
|
||||||
"no-search-results": "इस सामग्री के लिए पूर्णपाठ खोज इंजन उपलब्ध नहीं है।",
|
|
||||||
"library-button-text": "स्वागत पृष्ठ पर जाएँ",
|
|
||||||
"home-button-text": "'{{BOOK_TITLE}}' के मुख्य पृष्ठ पर जाएँ",
|
|
||||||
"random-page-button-text": "यादृच्छिक रूप से चयनित पृष्ठ पर जाएँ",
|
|
||||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' खोजें",
|
|
||||||
"confusion-of-tongues": "विभिन्न भाषाओं की दो या दो से अधिक पुस्तकें खोज में भाग लेंगी, जिससे भ्रमित करने वाले परिणाम मिल सकते हैं।",
|
|
||||||
"welcome-page-overzealous-filter": "कोई परिणाम नहीं। क्या आप <a href=\"{{URL}}\">फ़िल्टर रीसेट करना</a> चाहेंगे?",
|
|
||||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">किविक्स</a> द्वारा संचालित",
|
|
||||||
"search": "खोजें",
|
|
||||||
"book-filtering-all-categories": "सब वर्ग",
|
|
||||||
"book-filtering-all-languages": "सभी भाषाएँ",
|
|
||||||
"count-of-matching-books": "{{COUNT}} पुस्तक/पुस्तकें",
|
|
||||||
"download": "डाउनलोड",
|
|
||||||
"direct-download-link-text": "प्रत्यक्ष",
|
|
||||||
"direct-download-alt-text": "प्रत्यक्षत: डाउनलोड",
|
|
||||||
"hash-download-link-text": "शा256 हैश",
|
|
||||||
"hash-download-alt-text": "हैश डाउनलोड करें",
|
|
||||||
"magnet-link-text": "MAGNET लिंक",
|
|
||||||
"magnet-alt-text": "MAGNET डाउनलोड करें",
|
|
||||||
"torrent-download-link-text": "टोरेंट फ़ाइल",
|
|
||||||
"torrent-download-alt-text": "टोरेंट डाउनलोड करें",
|
|
||||||
"library-opds-feed-all-entries": "लाइब्रेरी ओपीडीएस फ़ीड - सभी प्रविष्टियाँ",
|
|
||||||
"filter-by-tag": "टैग \"{{TAG}}\" द्वारा फ़िल्टर करें",
|
|
||||||
"stop-filtering-by-tag": "\"{{TAG}}\" टैग द्वारा फ़िल्टर करना बंद करें",
|
|
||||||
"library-opds-feed-parameterised": "लाइब्रेरी ओपीडीएस फ़ीड - मिलान वाली प्रविष्टियाँ {{#LANG}}\nभाषा: {{LANG}} {/LANG}}{{#CATEGORY}}\nश्रेणी: {{CATEGORY}} {/CATEGORY}} {{#TAG}}\nटैग: {{TAG}} {{/TAG}}{{#Q}}\nक्वेरी: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
|
|
||||||
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
|
|
||||||
"download-links-title": "पुस्तक डाउनलोड करें",
|
|
||||||
"preview-book": "पूर्वावलोकन",
|
|
||||||
"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,15 +17,5 @@
|
|||||||
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
|
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
|
||||||
"random-page-button-text": "Բացել պատահական էջ",
|
"random-page-button-text": "Բացել պատահական էջ",
|
||||||
"searchbox-tooltip": "Որոնել '{{BOOK_TITLE}}'֊ում",
|
"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": "Այլ"
|
|
||||||
}
|
}
|
||||||
+10
-59
@@ -1,43 +1,10 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kelson",
|
|
||||||
"McDutchie"
|
"McDutchie"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "interlingua",
|
"welcome-page-overzealous-filter": "Nulle resultato. Vole tu <a href=\"?lang=\">reinitialisar le filtro</a>?",
|
||||||
"suggest-full-text-search": "continente '{{{SEARCH_TERMS}}}'...",
|
|
||||||
"no-such-book": "Necun tal libro: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "Troppo de libros demandate ({{NB_BOOKS}}); le limite es {{LIMIT}}",
|
|
||||||
"no-book-found": "Necun libro corresponde al criterios de selection",
|
|
||||||
"url-not-found": "Le URL reuqestate \"{{url}}\" non ha essite trovate sur iste servitor.",
|
|
||||||
"suggest-search": "Facer un recerca in texto complete de <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
|
||||||
"random-article-failure": "Ups! Non poteva eliger un articulo aleatori :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} non es un request valide pro contento crude.",
|
|
||||||
"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}}",
|
|
||||||
"400-page-title": "Requesta invalide",
|
|
||||||
"400-page-heading": "Requesta invalide",
|
|
||||||
"404-page-title": "Contento non trovate",
|
|
||||||
"404-page-heading": "Non trovate",
|
|
||||||
"500-page-title": "Error interne del servitor",
|
|
||||||
"500-page-heading": "Error interne del servitor",
|
|
||||||
"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",
|
|
||||||
"searchbox-tooltip": "Cercar '{{BOOK_TITLE}}'",
|
|
||||||
"confusion-of-tongues": "Duo o plus libros in differente linguas participarea in le recerca, lo que pote menar a resultatos confuse.",
|
|
||||||
"welcome-page-overzealous-filter": "Nulle resultato. Vole tu <a href=\"{{URL}}\">reinitialisar le filtro</a>?",
|
|
||||||
"powered-by-kiwix-html": "Actionate per <a href=\"https://kiwix.org\">Kiwix</a>",
|
"powered-by-kiwix-html": "Actionate per <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||||
"search": "Cercar",
|
"search": "Cercar",
|
||||||
"book-filtering-all-categories": "Tote le categorias",
|
"book-filtering-all-categories": "Tote le categorias",
|
||||||
@@ -45,30 +12,14 @@
|
|||||||
"count-of-matching-books": "{{COUNT}} libro(s)",
|
"count-of-matching-books": "{{COUNT}} libro(s)",
|
||||||
"download": "Discargar",
|
"download": "Discargar",
|
||||||
"direct-download-link-text": "Directe",
|
"direct-download-link-text": "Directe",
|
||||||
"direct-download-alt-text": "Discargamento directe per HTTP(S)",
|
"direct-download-alt-text": "discargamento directe",
|
||||||
"hash-download-link-text": "Summa de controlo SHA-256",
|
"hash-download-link-text": "Hash SHA256",
|
||||||
"hash-download-alt-text": "Monstrar le summa de controlo SHA-256 del file",
|
"hash-download-alt-text": "hash del discargamento",
|
||||||
"magnet-link-text": "Ligamine Magnet",
|
"magnet-link-text": "Ligamine Magnet",
|
||||||
"magnet-alt-text": "Discargar con ligamine Magnet",
|
"magnet-alt-text": "ligamine \"magnet\" de discargamento",
|
||||||
"torrent-download-link-text": "BitTorrent",
|
"torrent-download-link-text": "File Torrent",
|
||||||
"torrent-download-alt-text": "Discargar per medio de BitTorrent",
|
"torrent-download-alt-text": "discargar Torrent",
|
||||||
"library-opds-feed-all-entries": "Fluxo OPDS del bibliotheca – Tote le entratas",
|
"library-opds-feed": "Fluxo OPDS del bibliotheca",
|
||||||
"filter-by-tag": "Filtrar per etiquetta “{{{TAG}}}”",
|
"filter-by-tag": "Filtrar per etiquetta \"{{TAG}}\"",
|
||||||
"stop-filtering-by-tag": "Non plus filtrar per etiquetta “{{{TAG}}}”",
|
"stop-filtering-by-tag": "Non plus filtrar per etiquetta \"{{TAG}}\""
|
||||||
"library-opds-feed-parameterised": "Fluxo OPDS del bibliotheca – Entratas correspondente a {{#LANG}}\nLingua {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtiquetta: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Benvenite al servitor Kiwix",
|
|
||||||
"download-links-heading": "Discargar ligamines pro <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Discargar libro",
|
|
||||||
"preview-book": "Previsualisation",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Accuratecy051",
|
|
||||||
"Ngostary2k",
|
|
||||||
"Oby Ezeilo"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "Bekee",
|
|
||||||
"suggest-full-text-search": "nwere {{{SEARCH_TERMS}}}'",
|
|
||||||
"no-such-book": "Enweghị akwụkwọ dị otú a: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "Arịrịọ ọtụtụ akwụkwọ ({{NB_BOOKS}}) ebe oke bụ {{LIMIT}}",
|
|
||||||
"no-book-found": "Ọ nweghị akwụkwọ dabara na nhọpụta nhọrọ",
|
|
||||||
"url-not-found": "Ahụghị URL a rịọrọ \"{{url}}\" na nkesa a.",
|
|
||||||
"suggest-search": "Mee ọchụchọ ederede zuru oke maka <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
|
||||||
"random-article-failure": "Ee! Ịhọrọ akụkọ enweghị usoro :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} abụghị arịrịọ ziri ezi maka ọdịnaya raw.",
|
|
||||||
"invalid-request": "Arịrịọ gbasara URL \"{{{url}}}\" e zighi ezi.",
|
|
||||||
"no-value-for-arg": "Ọ nweghị uru enyere maka arụmụka {{ARGUMENT}}",
|
|
||||||
"no-query": "Ọnweghị ajụjụ enyere.",
|
|
||||||
"raw-entry-not-found": "Enweghị ike ịchọta ntinye {{DATATYPE}} {{ENTRY}}",
|
|
||||||
"400-page-title": "Arịrịọ na-ezighi ezi",
|
|
||||||
"400-page-heading": "Arịrịọ na-ezighi ezi",
|
|
||||||
"404-page-title": "Ahụghị ọdịnaya",
|
|
||||||
"404-page-heading": "Ahụghị",
|
|
||||||
"500-page-title": "Mperi Sava Ime",
|
|
||||||
"500-page-heading": "Mperi Sava Ime",
|
|
||||||
"500-page-text": "Enwere mperi ihe nkesa dị n'ime. Ọ dị anyị nwute na nke ahụ :/",
|
|
||||||
"fulltext-search-unavailable": "Ọchịchọ ederede zuru ezu adịghị",
|
|
||||||
"no-search-results": "Igwe nchọta ederede zuru oke adịghị maka ọdịnaya a.",
|
|
||||||
"search-results-page-title": "Chọọ: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Rịzọlt ga <b>{{START}}-{{END}}</b> nke <b>{{COUNT}}</b> maka <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"empty-search-results-page-header": "Ọnweghị rịzọlt ahụrụ maka <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"search-result-book-info": "sitere na {{BOOK_TITLE}}",
|
|
||||||
"word-count": "Okwu {{COUNT}}",
|
|
||||||
"library-button-text": "Gaa na ibe nnabata",
|
|
||||||
"home-button-text": "Gaa na isi ibe nke '{{BOOK_TITLE}}'",
|
|
||||||
"random-page-button-text": "Gaa na ibe ahọpụtara enweghị usoro",
|
|
||||||
"searchbox-tooltip": "Chọọ '{{BOOK_TITLE}}'",
|
|
||||||
"confusion-of-tongues": "Akwụkwọ abụọ ma ọ bụ karịa n'asụsụ dị iche iche ga-esonye na nchọ, nke nwere ike ibute nsonaazụ mgbagwoju anya.",
|
|
||||||
"welcome-page-overzealous-filter": "Enweghị nsonaazụ. Ọ ga-amasị gị <a href=\"{{URL}}\">ịtọgharịa nzacha</a> ?",
|
|
||||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> kwadoro ya",
|
|
||||||
"search": "Chọọ",
|
|
||||||
"book-filtering-all-categories": "Nkeji niile",
|
|
||||||
"book-filtering-all-languages": "Asụsụ niile",
|
|
||||||
"count-of-matching-books": "akwụkwọ {{COUNT}}",
|
|
||||||
"download": "Budata",
|
|
||||||
"direct-download-link-text": "Gosi",
|
|
||||||
"direct-download-alt-text": "nbudata ozugbo",
|
|
||||||
"hash-download-link-text": "Sha256 hash",
|
|
||||||
"hash-download-alt-text": "budata hash",
|
|
||||||
"magnet-link-text": "Njikọ magnet",
|
|
||||||
"magnet-alt-text": "ibudata magnet",
|
|
||||||
"torrent-download-link-text": " faịlụ nke Torrent",
|
|
||||||
"torrent-download-alt-text": "Budata torrent",
|
|
||||||
"library-opds-feed-all-entries": "Ọbá akwụkwọ OPDS Feed - Ihe niile",
|
|
||||||
"filter-by-tag": "Wepụta site na mkpado \"{{TAG}}\"",
|
|
||||||
"stop-filtering-by-tag": "Kwụsị nzacha site na mkpado \"{{TAG}}\"",
|
|
||||||
"library-opds-feed-parameterised": "Ọbá akwụkwọ OPDS nri - ndenye dabara na {{#LANG}}\nAsụsụ: {{LANG}} {{/LANG}}{{#CATEGORY}}\n Kategori: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\n Ajụjụ: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Nabata na Kiwix Server",
|
|
||||||
"download-links-heading": "Budata njikọ maka <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Budata akwụkwọ",
|
|
||||||
"preview-book": "Ziwe nkirimaàtụ̀",
|
|
||||||
"unknown-error": "amaghị m njehie"
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,17 @@
|
|||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Albano",
|
"Albano",
|
||||||
"Beta16",
|
"Beta16"
|
||||||
"Luca.favorido",
|
|
||||||
"McDutchie"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "italiano",
|
"name": "italiano",
|
||||||
"suggest-full-text-search": "contenente '{{{SEARCH_TERMS}}}'...",
|
"suggest-full-text-search": "contenente '{{{SEARCH_TERMS}}}'...",
|
||||||
"no-such-book": "Nessun libro con questo nome: {{BOOK_NAME}}",
|
"no-such-book": "Nessun libro del genere: {{BOOK_NAME}}",
|
||||||
"too-many-books": "Troppi libri richiesti ({{NB_BOOKS}}) dove il limite è {{LIMIT}}",
|
"too-many-books": "Troppi libri richiesti ({{NB_BOOKS}}) dove il limite è {{LIMIT}}",
|
||||||
"no-book-found": "Nessun libro corrisponde ai criteri di selezione",
|
"no-book-found": "Nessun libro corrisponde ai criteri di selezione",
|
||||||
"url-not-found": "L'URL richiesto \"{{url}}\" non è stato trovato in questo server.",
|
"url-not-found": "L'URL richiesto \"{{url}}\" non è stato trovato in questo server.",
|
||||||
"suggest-search": "Effettua una ricerca di testo completo per <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
"suggest-search": "Effettua una ricerca di testo completo per <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||||
"random-article-failure": "Ops! Impossibile selezionare un articolo casuale :(",
|
"random-article-failure": "Ops! Impossibile selezionare un articolo casuale :(",
|
||||||
"invalid-request": "L'URL richiesto \"{{{url}}}\" non è una richiesta valida.",
|
|
||||||
"no-value-for-arg": "Nessun valore fornito per l'argomento {{ARGUMENT}}",
|
"no-value-for-arg": "Nessun valore fornito per l'argomento {{ARGUMENT}}",
|
||||||
"400-page-title": "Richiesta non valida",
|
"400-page-title": "Richiesta non valida",
|
||||||
"400-page-heading": "Richiesta non valida",
|
"400-page-heading": "Richiesta non valida",
|
||||||
@@ -23,12 +20,6 @@
|
|||||||
"404-page-heading": "Non trovato",
|
"404-page-heading": "Non trovato",
|
||||||
"500-page-title": "Errore interno del server",
|
"500-page-title": "Errore interno del server",
|
||||||
"500-page-heading": "Errore interno del server",
|
"500-page-heading": "Errore interno del server",
|
||||||
"500-page-text": "Si è verificato un errore interno del server. Ci dispiace :/",
|
|
||||||
"search-results-page-title": "Cerca: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Risultati <b>{{START}}-{{END}}</b> di <b>{{COUNT}}</b> per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"empty-search-results-page-header": "Non è stato trovato alcun risultato per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"search-result-book-info": "da {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} parole",
|
|
||||||
"library-button-text": "Vai alla pagina di benvenuto",
|
"library-button-text": "Vai alla pagina di benvenuto",
|
||||||
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
|
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
|
||||||
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
||||||
@@ -36,12 +27,5 @@
|
|||||||
"book-filtering-all-categories": "Tutte le categorie",
|
"book-filtering-all-categories": "Tutte le categorie",
|
||||||
"book-filtering-all-languages": "Tutte le lingue",
|
"book-filtering-all-languages": "Tutte le lingue",
|
||||||
"count-of-matching-books": "{{COUNT}} libro/i",
|
"count-of-matching-books": "{{COUNT}} libro/i",
|
||||||
"download": "Scarica",
|
"download": "Scarica"
|
||||||
"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",
|
|
||||||
"download-links-title": "Scarica libro",
|
|
||||||
"preview-book": "Anteprima",
|
|
||||||
"unknown-error": "Errore sconosciuto"
|
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,5 @@
|
|||||||
"magnet-link-text": "マグネットリンク",
|
"magnet-link-text": "マグネットリンク",
|
||||||
"magnet-alt-text": "マグネットをダウンロード",
|
"magnet-alt-text": "マグネットをダウンロード",
|
||||||
"torrent-download-link-text": "Torrentファイル",
|
"torrent-download-link-text": "Torrentファイル",
|
||||||
"torrent-download-alt-text": "Torrentをダウンロード",
|
"torrent-download-alt-text": "Torrentをダウンロード"
|
||||||
"preview-book": "プレビュー"
|
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,5 @@
|
|||||||
"500-page-title": "내부 서버 오류",
|
"500-page-title": "내부 서버 오류",
|
||||||
"500-page-heading": "내부 서버 오류",
|
"500-page-heading": "내부 서버 오류",
|
||||||
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
|
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
|
||||||
"random-page-button-text": "무작위로 선택된 문서로 이동",
|
"random-page-button-text": "무작위로 선택된 문서로 이동"
|
||||||
"hash-download-link-text": "SHA-256 체크섬",
|
|
||||||
"torrent-download-link-text": "비트토렌트",
|
|
||||||
"preview-book": "미리 보기"
|
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,26 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kelson",
|
|
||||||
"Robby",
|
"Robby",
|
||||||
"Volvox"
|
"Volvox"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "Lëtzebuergesch",
|
"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>",
|
"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 :(",
|
"random-article-failure": "Ups! Et konnt keen zoufällegen Artikel ausgewielt ginn :(",
|
||||||
"404-page-title": "Inhalt net fonnt",
|
"404-page-title": "Inhalt net fonnt",
|
||||||
"404-page-heading": "Net fonnt",
|
"404-page-heading": "Net fonnt",
|
||||||
"500-page-title": "Interne Feeler um Server",
|
"500-page-title": "Interne Feeler um Server",
|
||||||
"500-page-heading": "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 :/",
|
|
||||||
"fulltext-search-unavailable": "Volltext-Sich net verfügbar",
|
"fulltext-search-unavailable": "Volltext-Sich net verfügbar",
|
||||||
"search-results-page-title": "Sichen: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Resultater <b>{{START}}-{{END}}</b> vu(n) <b>{{COUNT}}</b> fir <b>„{{{SEARCH_PATTERN}}}“</b>",
|
|
||||||
"empty-search-results-page-header": "Keng Resultater fonnt fir <b>„{{{SEARCH_PATTERN}}}“</b>",
|
|
||||||
"search-result-book-info": "aus {{BOOK_TITLE}}",
|
|
||||||
"word-count": "{{COUNT}} Wierder",
|
|
||||||
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
|
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
|
||||||
"random-page-button-text": "Gitt op eng zoufälleg gewielte Säit",
|
"random-page-button-text": "Gitt op eng zoufälleg gewielte Säit",
|
||||||
"searchbox-tooltip": "No '{{BOOK_TITLE}}' sichen",
|
"searchbox-tooltip": "No '{{BOOK_TITLE}}' sichen",
|
||||||
"welcome-page-overzealous-filter": "Kee Resultat. Wëllt Dir <a href=\"{{URL}}\">de Filter zrécksetzen</a>?",
|
"welcome-page-overzealous-filter": "Kee Resultat. Wëllt Dir <a href=\"?lang=\">de Filter zrécksetzen</a>?",
|
||||||
"search": "Sichen",
|
"search": "Sichen",
|
||||||
"book-filtering-all-categories": "All Kategorien",
|
"book-filtering-all-categories": "All Kategorien",
|
||||||
"book-filtering-all-languages": "All Sproochen",
|
"book-filtering-all-languages": "All Sproochen",
|
||||||
"count-of-matching-books": "{{COUNT}} Buch/Bicher",
|
"count-of-matching-books": "{{COUNT}} Buch/Bicher",
|
||||||
"download": "Eroflueden",
|
"download": "Eroflueden",
|
||||||
"direct-download-link-text": "Direkt",
|
"direct-download-link-text": "Direkt"
|
||||||
"torrent-download-link-text": "BitTorrent",
|
|
||||||
"download-links-title": "Buch eroflueden",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
+11
-43
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Bjankuloski06",
|
"Bjankuloski06"
|
||||||
"Kelson"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "македонски",
|
"name": "македонски",
|
||||||
@@ -14,7 +13,6 @@
|
|||||||
"suggest-search": "Побарајте го <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> по целиот текст",
|
"suggest-search": "Побарајте го <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> по целиот текст",
|
||||||
"random-article-failure": "Упс! Не успеав да изберам случајна статија :(",
|
"random-article-failure": "Упс! Не успеав да изберам случајна статија :(",
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} не претставува важечко барање за сирова содржина.",
|
"invalid-raw-data-type": "{{DATATYPE}} не претставува важечко барање за сирова содржина.",
|
||||||
"invalid-request": "Побараната URL „{{{url}}}“ не претставува важечко барање.",
|
|
||||||
"no-value-for-arg": "Нема укажано вредност за аргументот {{ARGUMENT}}",
|
"no-value-for-arg": "Нема укажано вредност за аргументот {{ARGUMENT}}",
|
||||||
"no-query": "Не е укажано барање.",
|
"no-query": "Не е укажано барање.",
|
||||||
"raw-entry-not-found": "Не можам да ја најдам {{DATATYPE}}-ставката {{ENTRY}}",
|
"raw-entry-not-found": "Не можам да ја најдам {{DATATYPE}}-ставката {{ENTRY}}",
|
||||||
@@ -24,20 +22,14 @@
|
|||||||
"404-page-heading": "Не е најдено",
|
"404-page-heading": "Не е најдено",
|
||||||
"500-page-title": "Внатрешна грешка во опслужувачот",
|
"500-page-title": "Внатрешна грешка во опслужувачот",
|
||||||
"500-page-heading": "Внатрешна грешка во опслужувачот",
|
"500-page-heading": "Внатрешна грешка во опслужувачот",
|
||||||
"500-page-text": "Настана внатрешна грешка во опслужувачот. Жал ни е :/",
|
|
||||||
"fulltext-search-unavailable": "Целотекстното пребарување е недостапно",
|
"fulltext-search-unavailable": "Целотекстното пребарување е недостапно",
|
||||||
"no-search-results": "Погонот за целотекстно пребарување не е достапен за оваа содржина.",
|
"no-search-results": "Погонот за целотекстно пребарување не е достапен за оваа содржина.",
|
||||||
"search-results-page-title": "Пребарување: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Исход <b>{{START}}-{{END}}</b> од <b>{{COUNT}}</b> за <b>„{{{SEARCH_PATTERN}}}“</b>",
|
|
||||||
"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}}“",
|
"home-button-text": "Оди на главната страница на „{{BOOK_TITLE}}“",
|
||||||
"random-page-button-text": "Оди на случајно избрана страница",
|
"random-page-button-text": "Оди на случајно избрана страница",
|
||||||
"searchbox-tooltip": "Пребарај го „{{BOOK_TITLE}}“",
|
"searchbox-tooltip": "Пребарај го „{{BOOK_TITLE}}“",
|
||||||
"confusion-of-tongues": "Во пребарувањето ќе учествуваат две или повеќе книги на различни јазици, што може да довете до збунувачки исход.",
|
"confusion-of-tongues": "Во пребарувањето ќе учествуваат две или повеќе книги на различни јазици, што може да довете до збунувачки исход.",
|
||||||
"welcome-page-overzealous-filter": "Нема исход. Дали би сакале да го <a href=\"{{URL}}\">поништите филтерот</a>?",
|
"welcome-page-overzealous-filter": "Нема исход. Дали би сакале да го <a href=\"?lang=\">поништите филтерот</a>?",
|
||||||
"powered-by-kiwix-html": "Овозможено од <a href=\"https://kiwix.org\">Кивикс</a>",
|
"powered-by-kiwix-html": "Овозможено од <a href=\"https://kiwix.org\">Кивикс</a>",
|
||||||
"search": "Пребарај",
|
"search": "Пребарај",
|
||||||
"book-filtering-all-categories": "Сите категории",
|
"book-filtering-all-categories": "Сите категории",
|
||||||
@@ -45,38 +37,14 @@
|
|||||||
"count-of-matching-books": "{{COUNT}} книги",
|
"count-of-matching-books": "{{COUNT}} книги",
|
||||||
"download": "Преземи",
|
"download": "Преземи",
|
||||||
"direct-download-link-text": "Непосредно",
|
"direct-download-link-text": "Непосредно",
|
||||||
"direct-download-alt-text": "Непосредно преземање преку HTTP(S)",
|
"direct-download-alt-text": "непосредно преземање",
|
||||||
"hash-download-link-text": "Контролен збир Sha256",
|
"hash-download-link-text": "Sha256-тараба",
|
||||||
"hash-download-alt-text": "Прикажи контролен збир SHA-256 на податотеката",
|
"hash-download-alt-text": "преземи тараба",
|
||||||
"magnet-link-text": "Магнетна врска",
|
"magnet-link-text": "Магнетна врска",
|
||||||
"magnet-alt-text": "Преземи преку Magnet-врска",
|
"magnet-alt-text": "преземи магнет",
|
||||||
"torrent-download-link-text": "BitTorrent",
|
"torrent-download-link-text": "Торентна податотека",
|
||||||
"torrent-download-alt-text": "Преземи преку BitTorrent",
|
"torrent-download-alt-text": "преземи торент",
|
||||||
"library-opds-feed-all-entries": "Библиотечен тековник на OPDS — Сите ставки",
|
"library-opds-feed": "Библиотечен OPDS-тековник",
|
||||||
"filter-by-tag": "Филтрирај по ознаката „{{{TAG}}}“",
|
"filter-by-tag": "Филтрирај по ознаката „{{TAG}}“",
|
||||||
"stop-filtering-by-tag": "Запри филтрирање по ознаката „{{{TAG}}}“",
|
"stop-filtering-by-tag": "Запри филтрирање по ознаката „{{TAG}}“"
|
||||||
"library-opds-feed-parameterised": "Библиотечен тековник на OPDS — ставки што одговараат на {{#LANG}}\nЈазик: {{LANG}} {{/LANG}}{{#CATEGORY}}\nКатегорија: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nОзнака: {{TAG}} {{/TAG}}{{#Q}}\nБарање: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Добре дојдовте на Опслужувачот на Кивикс",
|
|
||||||
"download-links-heading": "Врски за преземање на <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Преземи книга",
|
|
||||||
"preview-book": "Преглед",
|
|
||||||
"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": "друго"
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Tofeiku"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "Bahasa Melayu",
|
|
||||||
"404-page-heading": "Tidak Dijumpai",
|
|
||||||
"500-page-title": "Ralat Pelayan Dalaman",
|
|
||||||
"500-page-heading": "Ralat Pelayan Dalaman",
|
|
||||||
"library-button-text": "Pergi ke laman selamat datang",
|
|
||||||
"searchbox-tooltip": "Cari '{{BOOK_TITLE}}'",
|
|
||||||
"search": "Cari",
|
|
||||||
"book-filtering-all-categories": "Semua kategori",
|
|
||||||
"book-filtering-all-languages": "Semua bahasa",
|
|
||||||
"download": "Muat turun",
|
|
||||||
"direct-download-link-text": "Langsung",
|
|
||||||
"direct-download-alt-text": "muat turun langsung",
|
|
||||||
"download-links-title": "Muat turun buku"
|
|
||||||
}
|
|
||||||
+11
-44
@@ -1,42 +1,15 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kelson",
|
|
||||||
"McDutchie",
|
"McDutchie",
|
||||||
"Siebrand",
|
|
||||||
"Vistaus"
|
"Vistaus"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "Nederlands",
|
"too-many-books": "Er zijn teveel boeken opgevraagd ({{NB_BOOKS}}). Het limiet is {{LIMIT}}.",
|
||||||
"suggest-full-text-search": "bevat ‘{{{SEARCH_TERMS}}}’…",
|
|
||||||
"no-such-book": "Boek bestaat niet: {{BOOK_NAME}}",
|
|
||||||
"too-many-books": "Er zijn teveel boeken opgevraagd ({{NB_BOOKS}}). De limiet is {{LIMIT}}.",
|
|
||||||
"no-book-found": "Er zijn geen boeken die overeenkomen met de zoekcriteria",
|
"no-book-found": "Er zijn geen boeken die overeenkomen met de zoekcriteria",
|
||||||
"url-not-found": "De opgevraagde URL “{{url}}” is niet gevonden op deze server.",
|
|
||||||
"suggest-search": "In volledige tekst zoeen naar <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
|
||||||
"random-article-failure": "Oeps! Kan geen willekeurig artikel kiezen :(",
|
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} is geen geldig verzoek voor onbewerkte inhoud.",
|
|
||||||
"no-value-for-arg": "Er is geen waarde opgegeven bij {{ARGUMENT}}",
|
"no-value-for-arg": "Er is geen waarde opgegeven bij {{ARGUMENT}}",
|
||||||
"no-query": "Er is geen zoekterm opgegeven.",
|
"no-query": "Er is geen zoekterm opgegeven.",
|
||||||
"raw-entry-not-found": "Kan het {{DATATYPE}}-item {{ENTRY}} niet vinden",
|
"welcome-page-overzealous-filter": "Geen resultaat. Wilt u <a href=\"?lang=\">het filter resetten</a>?",
|
||||||
"400-page-title": "Ongeldig verzoek",
|
|
||||||
"400-page-heading": "Ongeldig verzoek",
|
|
||||||
"404-page-title": "Inhoud niet gevonden",
|
|
||||||
"404-page-heading": "Niet gevonden",
|
|
||||||
"500-page-title": "Interne serverfout",
|
|
||||||
"500-page-heading": "Interne serverfout",
|
|
||||||
"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-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",
|
|
||||||
"searchbox-tooltip": "Naar ‘{{BOOK_TITLE}}’ zoeken",
|
|
||||||
"confusion-of-tongues": "Er zouden twee of meer boeken in verschillende talen deelnemen aan de zoekopdracht, wat tot verwarrende resultaten kan leiden.",
|
|
||||||
"welcome-page-overzealous-filter": "Geen resultaat. Wilt u <a href=\"{{URL}}\">het filter resetten</a>?",
|
|
||||||
"powered-by-kiwix-html": "Mogelijk gemaakt door <a href=\"https://kiwix.org\">Kiwix</a>",
|
"powered-by-kiwix-html": "Mogelijk gemaakt door <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||||
"search": "Zoeken",
|
"search": "Zoeken",
|
||||||
"book-filtering-all-categories": "Alle categorieën",
|
"book-filtering-all-categories": "Alle categorieën",
|
||||||
@@ -44,20 +17,14 @@
|
|||||||
"count-of-matching-books": "{{COUNT}} boek(en)",
|
"count-of-matching-books": "{{COUNT}} boek(en)",
|
||||||
"download": "Downloaden",
|
"download": "Downloaden",
|
||||||
"direct-download-link-text": "Direct",
|
"direct-download-link-text": "Direct",
|
||||||
"direct-download-alt-text": "Direct downloaden via HTTP(S)",
|
"direct-download-alt-text": "directe download",
|
||||||
"hash-download-link-text": "SHA-256-controlesom",
|
"hash-download-link-text": "SHA256-hash",
|
||||||
"hash-download-alt-text": "De SHA-256-controlesom van het bestand weergeven",
|
"hash-download-alt-text": "controlesom (hash) van de download",
|
||||||
"magnet-link-text": "Magnet-link",
|
"magnet-link-text": "Magnet-link",
|
||||||
"magnet-alt-text": "Downloaden via Magnet-link",
|
"magnet-alt-text": "magnet-link van de download",
|
||||||
"torrent-download-link-text": "BitTorrent",
|
"torrent-download-link-text": "Torrent-bestand",
|
||||||
"torrent-download-alt-text": "Downloaden via BitTorrent",
|
"torrent-download-alt-text": "torrent downloaden",
|
||||||
"library-opds-feed-all-entries": "OPDS-feed bibliotheek: alle vermeldingen",
|
"library-opds-feed": "OPDS-feed van de bibliotheek",
|
||||||
"filter-by-tag": "Filteren op label “{{TAG}}”",
|
"filter-by-tag": "Filteren op tag \"{{TAG}}\"",
|
||||||
"stop-filtering-by-tag": "Niet meer filteren op label “{{TAG}}”",
|
"stop-filtering-by-tag": "Stoppen met filteren op tag \"{{TAG}}\""
|
||||||
"library-opds-feed-parameterised": "OPDS-feed bibliotheek: vermeldingen die overeenkomen met {{#LANG}}\nTaal: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nLabel: {{TAG}} {{/TAG}}{{#Q}}\nZoekopdracht: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
|
|
||||||
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Boek downloaden",
|
|
||||||
"preview-book": "Voorvertoning",
|
|
||||||
"unknown-error": "Onbekende fout"
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Amire80",
|
|
||||||
"Lancine.kounfantoh.fofana"
|
"Lancine.kounfantoh.fofana"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -30,21 +29,5 @@
|
|||||||
"random-page-button-text": "ߕߊ߯ ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ߫ ߛߎߥߊ߲ߘߌߣߍ߲ ߠߎ߬ ߞߊ߲߬",
|
"random-page-button-text": "ߕߊ߯ ߓߍ߲߬ߛߋ߲߬ߡߊ߬ ߞߐߜߍ߫ ߛߎߥߊ߲ߘߌߣߍ߲ ߠߎ߬ ߞߊ߲߬",
|
||||||
"searchbox-tooltip": "ߕߌߙߌ߲ߠߌ߲ {{BOOK_TITLE}}",
|
"searchbox-tooltip": "ߕߌߙߌ߲ߠߌ߲ {{BOOK_TITLE}}",
|
||||||
"confusion-of-tongues": "ߞߊ߬ߝߊ߫ ߝߌ߬ߟߊ߬ ߥߟߊ߫ ߦߙߌߞߊ ߞߊ߲߫ ߜߘߍ ߟߎ߬ ߘߐ߫߸ ߏ߬ ߟߎ߫ ߘߌߣߊ߬ ߕߘߍ߬ ߢߌߣߌ߲ߠߌ߲ ߘߐ߫߸ ߡߍ߲ ߠߎ߬ ߛߌ߫ ߞߣߐ߬ߝߟߌ ߟߊߘߏ߲߬ ߠߊ߫ ߞߐߝߟߌ ߘߐ߫.",
|
"confusion-of-tongues": "ߞߊ߬ߝߊ߫ ߝߌ߬ߟߊ߬ ߥߟߊ߫ ߦߙߌߞߊ ߞߊ߲߫ ߜߘߍ ߟߎ߬ ߘߐ߫߸ ߏ߬ ߟߎ߫ ߘߌߣߊ߬ ߕߘߍ߬ ߢߌߣߌ߲ߠߌ߲ ߘߐ߫߸ ߡߍ߲ ߠߎ߬ ߛߌ߫ ߞߣߐ߬ߝߟߌ ߟߊߘߏ߲߬ ߠߊ߫ ߞߐߝߟߌ ߘߐ߫.",
|
||||||
"welcome-page-overzealous-filter": "ߞߐߝߟߌ߫ ߕߴߦߋ߲߬. ߊ߬ ߝߐ߫ ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ <a href=\"{{URL}}\">ߛߍ߲ߛߍ߲ߟߊ߲ ߘߐߛߌ߰ ߕߎ߲߯</a>؟",
|
"welcome-page-overzealous-filter": "ߞߐߝߟߌ߫ ߕߴߦߋ߲߬. ߊ߬ ߝߐ߫ ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ <a href=\"?lang=\">ߛߍ߲ߛߍ߲ߟߊ߲ ߘߐߛߌ߰ ߕߎ߲߯</a>؟"
|
||||||
"search": "ߢߌߣߌ߲ߠߌ߲",
|
|
||||||
"book-filtering-all-categories": "ߦߌߟߡߊ ߟߎ߬ ߓߍ߯",
|
|
||||||
"book-filtering-all-languages": "ߞߊ߲ ߠߎ߬ ߓߍ߯",
|
|
||||||
"count-of-matching-books": "ߞߊ߬ߝߊ {{COUNT}}(ߟߎ߫)",
|
|
||||||
"download": "ߟߊ߬ߖߌ߰ߒ߬ߞߎ߲߬ߠߌ߲",
|
|
||||||
"direct-download-link-text": "ߒߕߋߟߋ߲ߡߊ߬",
|
|
||||||
"direct-download-alt-text": "ߟߊ߬ߖߌ߰ߒ߬ߞߎ߲߬ߠߌ߲߫ ߝߊ߲ߞߢߊ",
|
|
||||||
"hash-download-alt-text": "ߤߊߛ߭ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
|
|
||||||
"magnet-link-text": "ߡߊߢߍߕ ߛߘߌ߬ߜߋ߲",
|
|
||||||
"magnet-alt-text": "ߡߊߢߍߕ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
|
|
||||||
"torrent-download-link-text": "ߕߏߙߍ߲ߕ ߞߐߕߐ߮",
|
|
||||||
"torrent-download-alt-text": "ߕߏߙߍ߲ߕ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
|
|
||||||
"welcome-to-kiwix-server": "ߌ ߣߌ߫ ߛߣߍ߫ ߞߥߌߞߛ ߡߊߛߐߟߊ߲ ߞߣߐ߫",
|
|
||||||
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> ߛߘߌ߬ߜߋ߲ ߠߊߖߌ߰ ߌ ߞߎ߲߬",
|
|
||||||
"download-links-title": "ߞߊ߬ߝߊ ߟߊߖߌ߰ ߌ ߞߎ߲߬",
|
|
||||||
"preview-book": "ߊ߬ ߘߐߜߍ߫ ߡߎߣߎ߲߬"
|
|
||||||
}
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Gouri",
|
|
||||||
"Psubhashish"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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}} କଞ୍ଚା ବିଷୟବସ୍ତୁ ପାଇଁ ଏକ ବ valid ଧ ଅନୁରୋଧ ନୁହେଁ |",
|
|
||||||
"no-value-for-arg": "ଯୁକ୍ତି ପାଇଁ କୌଣସି ମୂଲ୍ଯ଼ ପ୍ରଦାନ କରାଯାଇନାହିଁ ${{ARGUMENT}}",
|
|
||||||
"no-query": "କୌଣସି ପ୍ରଶ୍ନ ପ୍ରଦାନ କରାଯାଇନାହିଁ ।",
|
|
||||||
"raw-entry-not-found": "${{DATATYPE}} ${{ENTRY}} କୁ ଖୋଜି ପାରୁନାହିଁ|",
|
|
||||||
"400-page-title": "ଅବୈଧ ଅନୁରୋଧ",
|
|
||||||
"400-page-heading": "ଅବୈଧ ଅନୁରୋଧ",
|
|
||||||
"404-page-title": "ବିଷଯ଼ବସ୍ତୁ ମିଳୁନାହିଁ",
|
|
||||||
"404-page-heading": "ମିଳିଲାନାହିଁ",
|
|
||||||
"500-page-title": "ଆନ୍ତରିକ ସର୍ଭର ତୃଟି",
|
|
||||||
"500-page-heading": "ଆନ୍ତରିକ ସର୍ଭର ତୃଟି",
|
|
||||||
"fulltext-search-unavailable": "ପୂର୍ଣ୍ଣ ପାଠ ସନ୍ଧାନ ଉପଲବ୍ଧ ନାହିଁ |",
|
|
||||||
"no-search-results": "ଏହି ବିଷୟବସ୍ତୁ ପାଇଁ ଫୁଲ୍ ଟେକ୍ସଟ୍ ସର୍ଚ୍ଚ ଇଞ୍ଜିନ୍ ଉପଲବ୍ଧ ନାହିଁ |",
|
|
||||||
"library-button-text": "ସ୍ୱାଗତ ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |",
|
|
||||||
"home-button-text": "'{{BOOK_TITLE}}' ର ମୁଖ୍ୟ ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |",
|
|
||||||
"random-page-button-text": "ଏକ ଅନିୟମିତ ଭାବରେ ମନୋନୀତ ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |",
|
|
||||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' ସନ୍ଧାନ କରନ୍ତୁ |",
|
|
||||||
"confusion-of-tongues": "ବିଭିନ୍ନ ଭାଷାରେ ଦୁଇ କିମ୍ବା ଅଧିକ ପୁସ୍ତକ ସର୍ଚ୍ଚରେ ଅଂଶଗ୍ରହଣ କରିବେ ଯାହା ବିଭ୍ରାନ୍ତିକର ଫଳାଫଳ ଆଣିପାରେ |",
|
|
||||||
"welcome-page-overzealous-filter": "କୌଣସି ଫଳାଫଳ ନାହିଁ | ଆପଣ <a href=\"{{URL}}\">ଫିଲ୍ଟର ପୁନ res ସେଟ୍</a> କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?",
|
|
||||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">କିୱିକ୍ସ</a> ଦ୍ୱାରା ଚାଳିତ |",
|
|
||||||
"search": "ଖୋଜନ୍ତୁ",
|
|
||||||
"book-filtering-all-categories": "ସମସ୍ତ ବର୍ଗ |",
|
|
||||||
"book-filtering-all-languages": "ସମସ୍ତ ଭାଷା",
|
|
||||||
"count-of-matching-books": "{{COUNT}} ପୁସ୍ତକ (ଗୁଡିକ)",
|
|
||||||
"download": "ଡାଉନଲୋଡ଼",
|
|
||||||
"direct-download-link-text": "ସିଧାସଳଖ |",
|
|
||||||
"direct-download-alt-text": "ସିଧାସଳଖ ଡାଉନଲୋଡ୍ |",
|
|
||||||
"hash-download-link-text": "Sha256 ହ୍ୟାସ୍ |",
|
|
||||||
"hash-download-alt-text": "ହ୍ୟାସ୍ ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
|
|
||||||
"magnet-link-text": "ଚୁମ୍ବକୀୟ ଲିଙ୍କ୍",
|
|
||||||
"magnet-alt-text": "ଚୁମ୍ବକ ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
|
|
||||||
"torrent-download-link-text": "ଟୋରେଣ୍ଟ ଫାଇଲ୍ |",
|
|
||||||
"torrent-download-alt-text": "torrent ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
|
|
||||||
"library-opds-feed-all-entries": "ଲାଇବ୍ରେରୀ OPDS ଫିଡ୍ - ସମସ୍ତ ଏଣ୍ଟ୍ରିଗୁଡିକ |",
|
|
||||||
"filter-by-tag": "ଟ୍ୟାଗ୍ ଦ୍ୱାରା ଫିଲ୍ଟର୍ \"{{TAG}}\"",
|
|
||||||
"stop-filtering-by-tag": "\"${{TAG}}\" ଟ୍ୟାଗ୍ ଦ୍ୱାରା ଫିଲ୍ଟର କରିବା ବନ୍ଦ କରନ୍ତୁ |",
|
|
||||||
"library-opds-feed-parameterised": "ଲାଇବ୍ରେରୀ OPDS ଫିଡ୍ - ଏଣ୍ଟ୍ରିଗୁଡିକ {{#LANG}}! N! ଭାଷା! ${{LANG}} ! {#TAG}} Category: ${{CATEGORY}} ଟ୍ୟାଗ୍: ${{TAG}} {{/ TAG}} {{# Q}}! ପ୍ରଶ୍ନ: {{Q}} {{/ Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Kiwix ସର୍ଭରକୁ ସ୍ୱାଗତ |",
|
|
||||||
"download-links-heading": "<b><i>${{BOOK_TITLE}} for</i></b> ପାଇଁ ଲିଙ୍କ୍ ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
|
|
||||||
"download-links-title": "ବହି ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
|
|
||||||
"preview-book": "ପୂର୍ବରୂପ"
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Strebski",
|
"Strebski"
|
||||||
"WaldiSt"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "Polski",
|
"name": "Polski",
|
||||||
@@ -24,16 +23,5 @@
|
|||||||
"library-button-text": "Przejdź do strony powitalnej",
|
"library-button-text": "Przejdź do strony powitalnej",
|
||||||
"home-button-text": "Przejdź do głównej strony '{{BOOK_TITLE}}'",
|
"home-button-text": "Przejdź do głównej strony '{{BOOK_TITLE}}'",
|
||||||
"random-page-button-text": "Przejdź do losowo wybranej strony",
|
"random-page-button-text": "Przejdź do losowo wybranej strony",
|
||||||
"searchbox-tooltip": "Szukaj '{{BOOK_TITLE}}'",
|
"searchbox-tooltip": "Szukaj '{{BOOK_TITLE}}'"
|
||||||
"search": "Szukaj",
|
|
||||||
"book-filtering-all-categories": "Wszystkie Kategorie",
|
|
||||||
"book-filtering-all-languages": "Wszystkie języki",
|
|
||||||
"download": "Pobierz",
|
|
||||||
"direct-download-link-text": "Bezpośrednio",
|
|
||||||
"direct-download-alt-text": "bezpośrednie pobieranie",
|
|
||||||
"torrent-download-link-text": "Plik torrent",
|
|
||||||
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
|
|
||||||
"download-links-title": "Pobierz książkę",
|
|
||||||
"preview-book": "Podgląd",
|
|
||||||
"book-category.other": "Inne"
|
|
||||||
}
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Eduardoaddad",
|
|
||||||
"Obiru",
|
|
||||||
"Re demz",
|
|
||||||
"YoReaper"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"name": "Português",
|
|
||||||
"suggest-full-text-search": "Contendo '{{{SEARCH_TERMS}}}'...",
|
|
||||||
"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",
|
|
||||||
"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.",
|
|
||||||
"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",
|
|
||||||
"hash-download-link-text": "Verificação SHA-256",
|
|
||||||
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
|
|
||||||
"torrent-download-link-text": "BitTorrent",
|
|
||||||
"torrent-download-alt-text": "Baixar via BitTorrent",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
{
|
|
||||||
"@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"
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Amire80",
|
|
||||||
"Matthieu Gautier",
|
"Matthieu Gautier",
|
||||||
"Veloman Yunkan",
|
"Veloman Yunkan",
|
||||||
"Verdy p"
|
"Verdy p"
|
||||||
@@ -16,7 +15,6 @@
|
|||||||
"suggest-search": "Suggest a search when the URL points to a non existing article",
|
"suggest-search": "Suggest a search when the URL points to a non existing article",
|
||||||
"random-article-failure": "Failure of the random article selection procedure",
|
"random-article-failure": "Failure of the random article selection procedure",
|
||||||
"invalid-raw-data-type": "Invalid DATATYPE was used with the /raw endpoint (/raw/<book>/DATATYPE/...); allowed values are 'meta' and 'content'",
|
"invalid-raw-data-type": "Invalid DATATYPE was used with the /raw endpoint (/raw/<book>/DATATYPE/...); allowed values are 'meta' and 'content'",
|
||||||
"invalid-request": "Error text for malformed URLs.",
|
|
||||||
"no-value-for-arg": "Error text when no value has been provided for ARGUMENT in the request's query string",
|
"no-value-for-arg": "Error text when no value has been provided for ARGUMENT in the request's query string",
|
||||||
"no-query": "Error text when no query has been provided for fulltext search",
|
"no-query": "Error text when no query has been provided for fulltext search",
|
||||||
"raw-entry-not-found": "Entry requested via the /raw endpoint was not found",
|
"raw-entry-not-found": "Entry requested via the /raw endpoint was not found",
|
||||||
@@ -26,14 +24,8 @@
|
|||||||
"404-page-heading": "Heading of the 404 error page",
|
"404-page-heading": "Heading of the 404 error page",
|
||||||
"500-page-title": "Title of the 500 error page",
|
"500-page-title": "Title of the 500 error page",
|
||||||
"500-page-heading": "Heading of the 500 error page",
|
"500-page-heading": "Heading of the 500 error page",
|
||||||
"500-page-text": "Text of the 500 error page",
|
|
||||||
"fulltext-search-unavailable": "Title of the error page returned when search is attempted in a book without fulltext search database",
|
"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",
|
"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",
|
|
||||||
"search-results-page-header": "Header of the search results page",
|
|
||||||
"empty-search-results-page-header": "Header of the empty search results page",
|
|
||||||
"search-result-book-info": "Reference to the book where the search result belongs (this is displayed AFTER the search result)",
|
|
||||||
"word-count": "Word count information",
|
|
||||||
"library-button-text": "Tooltip of the button leading to the welcome page",
|
"library-button-text": "Tooltip of the button leading to the welcome page",
|
||||||
"home-button-text": "Tooltip of the button leading to the main page of a book",
|
"home-button-text": "Tooltip of the button leading to the main page of a book",
|
||||||
"random-page-button-text": "Tooltip of the button opening a randomly selected page",
|
"random-page-button-text": "Tooltip of the button opening a randomly selected page",
|
||||||
@@ -53,32 +45,12 @@
|
|||||||
"magnet-alt-text": "Hint for the icon of a magnet link",
|
"magnet-alt-text": "Hint for the icon of a magnet link",
|
||||||
"torrent-download-link-text": "Link text for downloading the torrent file",
|
"torrent-download-link-text": "Link text for downloading the torrent file",
|
||||||
"torrent-download-alt-text": "Hint for the icon of torrent download",
|
"torrent-download-alt-text": "Hint for the icon of torrent download",
|
||||||
"library-opds-feed-all-entries": "Hint for the library OPDS feed for all entries",
|
|
||||||
"filter-by-tag": "Hint for a link that would load results filtered by a single tag",
|
"filter-by-tag": "Hint for a link that would load results filtered by a single tag",
|
||||||
"stop-filtering-by-tag": "Tooltip for the button that cancels filtering by tag",
|
"stop-filtering-by-tag": "Tooltip for the button that cancels filtering by tag",
|
||||||
|
"library-opds-feed-all-entries": "Hint for the library OPDS feed for all entries",
|
||||||
"library-opds-feed-parameterised": "Hint for the library OPDS feed for filtered entries",
|
"library-opds-feed-parameterised": "Hint for the library OPDS feed for filtered entries",
|
||||||
"welcome-to-kiwix-server": "Title shown in browser's title bar/page tab",
|
"welcome-to-kiwix-server": "Title shown in browser's title bar/page tab",
|
||||||
"download-links-heading": "Heading for no-js download page",
|
"download-links-heading": "Heading for no-js download page",
|
||||||
"download-links-title": "Title for no-js download page",
|
"download-links-title": "Title for no-js download page",
|
||||||
"preview-book": "Tooltip of book-tile leading to the book",
|
"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",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,8 @@
|
|||||||
"authors": [
|
"authors": [
|
||||||
"Fenixs-ru",
|
"Fenixs-ru",
|
||||||
"Kareyac",
|
"Kareyac",
|
||||||
"Lutece398",
|
|
||||||
"Okras",
|
"Okras",
|
||||||
"Pacha Tchernof",
|
"Pacha Tchernof"
|
||||||
"Razno0",
|
|
||||||
"Rofiatmustapha12",
|
|
||||||
"Smavrina"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "русский",
|
"name": "русский",
|
||||||
@@ -20,7 +16,6 @@
|
|||||||
"suggest-search": "Выполните полнотекстовый поиск для <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
"suggest-search": "Выполните полнотекстовый поиск для <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||||
"random-article-failure": "Ой! Не удалось выбрать случайную статью :(",
|
"random-article-failure": "Ой! Не удалось выбрать случайную статью :(",
|
||||||
"invalid-raw-data-type": "{{DATATYPE}} не является допустимым запросом необработанного контента.",
|
"invalid-raw-data-type": "{{DATATYPE}} не является допустимым запросом необработанного контента.",
|
||||||
"invalid-request": "Запрошенный URL-адрес «{{{url}}}» не является допустимым запросом.",
|
|
||||||
"no-value-for-arg": "Не указано значение для аргумента {{ARGUMENT}}",
|
"no-value-for-arg": "Не указано значение для аргумента {{ARGUMENT}}",
|
||||||
"no-query": "Не предоставлен запрос.",
|
"no-query": "Не предоставлен запрос.",
|
||||||
"raw-entry-not-found": "Не удаётся найти запись {{ENTRY}} типа {{DATATYPE}}",
|
"raw-entry-not-found": "Не удаётся найти запись {{ENTRY}} типа {{DATATYPE}}",
|
||||||
@@ -30,51 +25,14 @@
|
|||||||
"404-page-heading": "Не найдено",
|
"404-page-heading": "Не найдено",
|
||||||
"500-page-title": "Внутренняя ошибка сервера",
|
"500-page-title": "Внутренняя ошибка сервера",
|
||||||
"500-page-heading": "Внутренняя ошибка сервера",
|
"500-page-heading": "Внутренняя ошибка сервера",
|
||||||
"500-page-text": "Произошла внутренняя ошибка сервера. Мы сожалеем об этом :/",
|
|
||||||
"fulltext-search-unavailable": "Полнотекстовый поиск недоступен",
|
"fulltext-search-unavailable": "Полнотекстовый поиск недоступен",
|
||||||
"no-search-results": "Полнотекстовая поисковая система недоступна для этого содержания.",
|
"no-search-results": "Полнотекстовая поисковая система недоступна для этого содержания.",
|
||||||
"search-results-page-title": "Поиск: {{SEARCH_PATTERN}}",
|
|
||||||
"search-results-page-header": "Результаты <b>{{START}}-{{END}}</b> из <b>{{COUNT}}</b> для <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
|
||||||
"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}}'",
|
"home-button-text": "Перейти на главную страницу '{{BOOK_TITLE}}'",
|
||||||
"random-page-button-text": "Перейти на случайно выбранную страницу",
|
"random-page-button-text": "Перейти на случайно выбранную страницу",
|
||||||
"searchbox-tooltip": "Искать '{{BOOK_TITLE}}'",
|
"searchbox-tooltip": "Искать '{{BOOK_TITLE}}'",
|
||||||
"confusion-of-tongues": "В поиске будут участвовать две или более книг на разных языках, что может привести к запутанным результатам.",
|
"confusion-of-tongues": "В поиске будут участвовать две или более книг на разных языках, что может привести к запутанным результатам.",
|
||||||
"welcome-page-overzealous-filter": "Безрезультатно. Хотите <a href=\"{{URL}}\">сбросить фильтр</a> ?",
|
|
||||||
"powered-by-kiwix-html": "При поддержке <a href=\"https://kiwix.org\">Kiwix</a>",
|
|
||||||
"search": "Найти",
|
|
||||||
"book-filtering-all-categories": "Все категории",
|
"book-filtering-all-categories": "Все категории",
|
||||||
"book-filtering-all-languages": "Все языки",
|
"book-filtering-all-languages": "Все языки",
|
||||||
"count-of-matching-books": "{{COUNT}} книг(и)",
|
"download": "Скачать"
|
||||||
"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": "Скачать по 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}}}\"",
|
|
||||||
"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": "Неизвестная ошибка",
|
|
||||||
"book-category.wikibooks": "Викиучебник",
|
|
||||||
"book-category.wikinews": "Викиновости",
|
|
||||||
"book-category.wikipedia": "Википедия",
|
|
||||||
"book-category.wikiquote": "Викицитатник",
|
|
||||||
"book-category.wikisource": "Викитека",
|
|
||||||
"book-category.wikispecies": "Викивиды",
|
|
||||||
"book-category.wikiversity": "Викиверситет",
|
|
||||||
"book-category.wikivoyage": "Викигид",
|
|
||||||
"book-category.wiktionary": "Викисловарь",
|
|
||||||
"book-category.other": "Другое"
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"@metadata": {
|
"@metadata": {
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kelson",
|
|
||||||
"L2212"
|
"L2212"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -29,28 +28,5 @@
|
|||||||
"home-button-text": "Bae a sa pàgina printzipale de '{{BOOK_TITLE}}'",
|
"home-button-text": "Bae a sa pàgina printzipale de '{{BOOK_TITLE}}'",
|
||||||
"random-page-button-text": "Bae a una pàgina seletzionada a manera casuale",
|
"random-page-button-text": "Bae a una pàgina seletzionada a manera casuale",
|
||||||
"searchbox-tooltip": "Chirca '{{BOOK_TITLE}}'",
|
"searchbox-tooltip": "Chirca '{{BOOK_TITLE}}'",
|
||||||
"confusion-of-tongues": "Duos o prus libros in limbas diferentes diant pigare parte a sa chirca, cosa chi diat pòdere causare resurtados confusionosos.",
|
"confusion-of-tongues": "Duos o prus libros in limbas diferentes diant pigare parte a sa chirca, cosa chi diat pòdere causare resurtados confusionosos."
|
||||||
"welcome-page-overzealous-filter": "Perunu resurtadu. Boles <a href=\"{{URL}}\">resetare su filtru</a>?",
|
|
||||||
"powered-by-kiwix-html": "Alimentadu dae <a href=\"https://kiwix.org\">Kiwix</a>",
|
|
||||||
"search": "Chirca",
|
|
||||||
"book-filtering-all-categories": "Totu sas categorias",
|
|
||||||
"book-filtering-all-languages": "Totu sas limbas",
|
|
||||||
"count-of-matching-books": "{{COUNT}} libru/os",
|
|
||||||
"download": "Iscàrriga",
|
|
||||||
"direct-download-link-text": "Diretu",
|
|
||||||
"direct-download-alt-text": "iscarrigamentu diretu",
|
|
||||||
"hash-download-link-text": "Hash SHA256",
|
|
||||||
"hash-download-alt-text": "hash de s'iscarrigamentu",
|
|
||||||
"magnet-link-text": "Ligàmene Magnet",
|
|
||||||
"magnet-alt-text": "ligàmene \"magnet\" de iscarrigamentu",
|
|
||||||
"torrent-download-link-text": "Documentu Torrent",
|
|
||||||
"torrent-download-alt-text": "iscàrriga su torrent",
|
|
||||||
"library-opds-feed-all-entries": "Flussu OPDS de sa biblioteca – Totu sos elementos",
|
|
||||||
"filter-by-tag": "Filtra pro eticheta \"{{TAG}}\"",
|
|
||||||
"stop-filtering-by-tag": "Non filtres prus pro eticheta \"{{TAG}}\"",
|
|
||||||
"library-opds-feed-parameterised": "Flussu OPDS de sa biblioteca - elementos chi currispondet cun {{#LANG}}\nLimba: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEticheta: {{TAG}} {{/TAG}}{{#Q}}\nChirca: {{Q}} {{/Q}}",
|
|
||||||
"welcome-to-kiwix-server": "Bene bènnidu a su serbidore de Kiwix",
|
|
||||||
"download-links-heading": "Ligàmenes de iscarrigamentu pro <b><i>{{BOOK_TITLE}}</i></b>",
|
|
||||||
"download-links-title": "Iscàrriga su libru",
|
|
||||||
"preview-book": "Antiprima"
|
|
||||||
}
|
}
|
||||||
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user