├── .env ├── .env.test ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .minus-x.json ├── .phan └── config.php ├── .stylelintignore ├── .stylelintrc.json ├── LICENCE ├── README.md ├── bin ├── console └── phpunit ├── composer.json ├── composer.lock ├── config ├── bundles.php ├── packages │ ├── cache.yaml │ ├── dev │ │ ├── monolog.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── mailer.yaml │ ├── prod │ │ ├── doctrine.yaml │ │ ├── monolog.yaml │ │ └── routing.yaml │ ├── routing.yaml │ ├── test │ │ ├── dama_doctrine_test_bundle.yaml │ │ ├── framework.yaml │ │ ├── monolog.yaml │ │ ├── twig.yaml │ │ └── web_profiler.yaml │ ├── toolforge.yaml │ └── twig.yaml ├── routes │ ├── annotations.yaml │ ├── dev │ │ ├── framework.yaml │ │ └── web_profiler.yaml │ └── routes.yaml ├── services.yaml └── services_test.yaml ├── docker ├── Dockerfile ├── apache │ ├── 000-default.conf │ └── ports.conf ├── docker-compose.yml └── php.ini ├── i18n ├── ar.json ├── as.json ├── ban.json ├── be-tarask.json ├── bn.json ├── br.json ├── ca.json ├── ce.json ├── cs.json ├── da.json ├── dag.json ├── de.json ├── diq.json ├── el.json ├── en.json ├── eo.json ├── es.json ├── et.json ├── eu.json ├── fa.json ├── fi.json ├── fr.json ├── gl.json ├── he.json ├── hu.json ├── ia.json ├── id.json ├── io.json ├── is.json ├── it.json ├── ja.json ├── ka.json ├── kg.json ├── ko.json ├── krc.json ├── ku-latn.json ├── lb.json ├── lt.json ├── mk.json ├── ml.json ├── ms.json ├── nb.json ├── nl.json ├── no.json ├── om.json ├── or.json ├── pa.json ├── pl.json ├── pt-br.json ├── pt.json ├── qqq.json ├── ro.json ├── ru.json ├── sje.json ├── sk.json ├── skr-arab.json ├── sl.json ├── sr-ec.json ├── su.json ├── sv.json ├── ta.json ├── te.json ├── tl.json ├── tr.json ├── uk.json ├── vec.json ├── vi.json ├── xmf.json ├── zh-hans.json └── zh-hant.json ├── migrations ├── Version20201216012133.php ├── Version20201216031438.php └── Version20201222094802.php ├── package-lock.json ├── package.json ├── phpcs.xml ├── phpunit.xml.dist ├── public ├── .user.ini ├── img │ ├── Wikisource-logo.svg │ ├── previous-ltr.svg │ └── previous-rtl.svg ├── index.php ├── robots.txt ├── styles.css └── toolinfo.json ├── resources ├── images │ └── Wikisource-logo.svg.png └── styles │ └── mediawiki.css ├── src ├── Book.php ├── BookCreator.php ├── BookProvider.php ├── Cleaner │ └── BookCleanerEpub.php ├── Command │ ├── CheckCommand.php │ ├── ExportCommand.php │ └── OpdsCommand.php ├── Controller │ ├── ExportController.php │ └── StatisticsController.php ├── Entity │ └── GeneratedBook.php ├── EpubCheck │ ├── EpubCheck.php │ ├── Location.php │ └── Result.php ├── EventSubscriber │ └── RateLimitSubscriber.php ├── Exception │ └── WsExportException.php ├── FileCache.php ├── FontProvider.php ├── Generator │ ├── AtomGenerator.php │ ├── ConvertGenerator.php │ ├── EpubGenerator.php │ └── FormatGenerator.php ├── GeneratorSelector.php ├── Kernel.php ├── OpdsBuilder.php ├── Page.php ├── PageParser.php ├── Picture.php ├── Refresh.php ├── Repository │ ├── CreditRepository.php │ └── GeneratedBookRepository.php ├── Util │ ├── Api.php │ ├── LoggingMiddleware.php │ ├── OnWikiConfig.php │ ├── Semaphore │ │ ├── Semaphore.php │ │ ├── SemaphoreHandle.php │ │ ├── UnixSemaphore.php │ │ └── UnixSemaphoreHandle.php │ └── Util.php └── Wikidata.php ├── templates ├── _month_year_selector.html.twig ├── base.html.twig ├── bundles │ └── TwigBundle │ │ └── Exception │ │ └── error.html.twig ├── export.html.twig └── statistics.html.twig └── tests ├── Book ├── BookProviderTest.php ├── GeneratorSelectorTest.php ├── PageParserTest.php ├── RefreshTest.php └── fixtures │ ├── Pacotilha_poetica │ └── Se_namora_por_gosto_ou_por_precisão.html │ └── Tales_of_Unrest │ └── Navigation.html ├── BookCreator ├── BookCreatorIntegrationTest.php └── BookCreatorTest.php ├── Cli └── BookCliTest.php ├── FontProviderTest.php ├── Generator └── EpubGeneratorTest.php ├── Http ├── BookTest.php └── StatTest.php ├── Repository └── GeneratedBookRepositoryTest.php ├── Util ├── ApiTest.php ├── LoggingMiddlewareTest.php ├── OnWikiConfigTest.php ├── Semaphore │ └── UnixSemaphoreTest.php └── UtilTest.php ├── WikidataTest.php └── bootstrap.php /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_ENV=prod 18 | APP_SECRET=2a025c1d3afd0715b3b86562f327b7a0 19 | #TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 20 | #TRUSTED_HOSTS='^(localhost|example\.com)$' 21 | ###< symfony/framework-bundle ### 22 | 23 | ###> doctrine/doctrine-bundle ### 24 | # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 25 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" 26 | # For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" 27 | # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml 28 | DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7 29 | ###< doctrine/doctrine-bundle ### 30 | 31 | ###> symfony/mailer ### 32 | MAILER_DSN=smtp://mail.tools.wmflabs.org:25 33 | ###< symfony/mailer ### 34 | 35 | # APP_TEMP_PATH=/tmp/wsexport 36 | APP_ENABLE_STATS=true 37 | APP_ENABLE_CACHE=false 38 | APP_CACHE_TTL=21600 39 | APP_TIMEOUT=120 40 | APP_MAIL_SENDER=tools.wsexport@tools.wmflabs.org 41 | APP_LOG_RECIPIENT_1=admin1@example.org 42 | APP_LOG_RECIPIENT_2=admin2@example.org 43 | APP_LOG_SUBJECT="[Wikisource Export]" 44 | EPUBCHECK_JAR="/usr/bin/epubcheck" 45 | # For production, REPLICAS_HOST should be *.web.db.svc.eqiad.wmflabs (or analytics instead of web) 46 | # For local environments, use "127.0.0.1" and "host.docker.internal" for docker. 47 | REPLICAS_HOST_S1="127.0.0.1" 48 | REPLICAS_HOST_S2="127.0.0.1" 49 | REPLICAS_HOST_S3="127.0.0.1" 50 | REPLICAS_HOST_S4="127.0.0.1" 51 | REPLICAS_HOST_S5="127.0.0.1" 52 | REPLICAS_HOST_S6="127.0.0.1" 53 | REPLICAS_HOST_S7="127.0.0.1" 54 | REPLICAS_HOST_S8="127.0.0.1" 55 | #For local environments, use any safe range of ports (such as 4711 for s1, 4712 for s2, and so on) 56 | REPLICAS_PORT_S1=4711 57 | REPLICAS_PORT_S2=4712 58 | REPLICAS_PORT_S3=4713 59 | REPLICAS_PORT_S4=4714 60 | REPLICAS_PORT_S5=4715 61 | REPLICAS_PORT_S6=4716 62 | REPLICAS_PORT_S7=4717 63 | REPLICAS_PORT_S8=4718 64 | REPLICAS_USERNAME="username" 65 | REPLICAS_PASSWORD="password123" 66 | APP_RATE_LIMIT=0 67 | APP_RATE_DURATION=0 68 | OAUTH_KEY= 69 | OAUTH_SECRET= 70 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | SYMFONY_DEPRECATIONS_HELPER=quiet[]=indirect&quiet[]=other 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | pull_request: 9 | branches: 10 | - '**' 11 | jobs: 12 | build: 13 | 14 | services: 15 | mysql: 16 | image: mysql:8.4 17 | env: 18 | MYSQL_DATABASE: wsexport_test 19 | MYSQL_ROOT_PASSWORD: testpwd 20 | ports: 21 | - '3306:3306' 22 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 23 | 24 | strategy: 25 | matrix: 26 | os: [ ubuntu-latest ] 27 | # Test against the lowest and highest PHP versions that we support. 28 | php: [ '8.2', '8.3' ] 29 | epubcheck: [ '5.2.1' ] 30 | 31 | env: 32 | APP_ENV: test 33 | DATABASE_URL: mysql://root:testpwd@127.0.0.1:3306/wsexport_test?serverVersion=8.4 34 | EPUBCHECK_JAR: /home/runner/work/ws-export/ws-export/epubcheck-${{matrix.epubcheck}}/epubcheck.jar 35 | 36 | runs-on: ${{matrix.os}} 37 | 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v1 41 | 42 | - name: Set up PHP 43 | uses: shivammathur/setup-php@v2 44 | with: 45 | php-version: ${{matrix.php}} 46 | extensions: pdo, mysql, pdo_mysql, sqlite3, imagick, zip 47 | coverage: none 48 | 49 | - name: Install Calibre, epubcheck, and fonts 50 | run: | 51 | sudo apt-get update -q 52 | sudo apt-get install fonts-freefont-ttf fonts-linuxlibertine fonts-dejavu-core fonts-gubbi fonts-opendyslexic libegl1 libopengl0 libxcb-cursor0 -y 53 | sudo mkdir /usr/share/desktop-directories/ 54 | sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin 55 | wget https://github.com/w3c/epubcheck/releases/download/v${{matrix.epubcheck}}/epubcheck-${{matrix.epubcheck}}.zip 56 | unzip epubcheck-${{matrix.epubcheck}}.zip 57 | realpath epubcheck-${{matrix.epubcheck}} 58 | 59 | - name: Install WSExport 60 | run: | 61 | composer install 62 | ./bin/console doctrine:migrations:migrate --no-interaction 63 | npm ci 64 | 65 | - name: Test 66 | run: | 67 | composer test 68 | ./bin/phpunit --exclude-group=exclude-from-ci 69 | npm test 70 | git status 71 | git status | grep "nothing added to commit" 72 | 73 | - name: Epubcheck 74 | run: | 75 | ./bin/console app:check --namespaces=0 --count=3 --lang en 76 | ./bin/console app:check --namespaces=0 --count=3 --lang ar 77 | ./bin/console app:check --namespaces=0 --count=3 --lang bn 78 | continue-on-error: true 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | temp/* 2 | !temp/.gitkeep 3 | composer.phar 4 | /config.php 5 | *.sqlite 6 | docker/.env 7 | docker-compose.override.yml 8 | 9 | public/opds/* 10 | !public/opds/.gitkeep 11 | 12 | ###> symfony/framework-bundle ### 13 | /.env.local 14 | /.env.local.php 15 | /.env.*.local 16 | /config/secrets/prod/prod.decrypt.private.php 17 | /public/bundles/ 18 | /src/.preload.php 19 | /var/ 20 | /vendor/ 21 | /node_modules/ 22 | ###< symfony/framework-bundle ### 23 | 24 | ###> symfony/phpunit-bridge ### 25 | .phpunit 26 | .phpunit.result.cache 27 | /phpunit.xml 28 | ###< symfony/phpunit-bridge ### 29 | -------------------------------------------------------------------------------- /.minus-x.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "./bin/.phpunit/phpunit-7.5-0/phpunit", 4 | "./bin/.phpunit/phpunit-8.3-0/phpunit", 5 | "./bin/.phpunit/phpunit-9.5-0/phpunit" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.phan/config.php: -------------------------------------------------------------------------------- 1 | '8.2', 10 | 11 | 'directory_list' => [ 12 | 'src', 13 | 'vendor', 14 | ], 15 | 16 | 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', 17 | 18 | 'exclude_analysis_directory_list' => [ 19 | 'vendor/', 20 | ], 21 | 22 | 'suppress_issue_types' => [ 23 | // Done by PHPCS, which can also read inline @var comment 24 | 'PhanUnreferencedUseNormal', 25 | // Symfony defines multiple classes sometimes, for backwards compatibility. 26 | 'PhanRedefinedExtendedClass', 27 | 'PhanRedefinedClassReference', 28 | // Bug between PHP 8.2 and 8.3? 29 | 'PhanPluginNeverReturnFunction' 30 | ], 31 | 32 | 'plugins' => [ 33 | 'PregRegexCheckerPlugin', 34 | 'UnusedSuppressionPlugin', 35 | 'DuplicateExpressionPlugin', 36 | 'LoopVariableReusePlugin', 37 | 'RedundantAssignmentPlugin', 38 | 'UnreachableCodePlugin', 39 | 'SimplifyExpressionPlugin', 40 | 'DuplicateArrayKeyPlugin', 41 | 'UseReturnValuePlugin', 42 | 'AddNeverReturnTypePlugin', 43 | ] 44 | ]; 45 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | vendor/ 3 | var/ 4 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-wikimedia" 3 | } 4 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption( [ '--env', '-e' ], null, true ); 25 | if ( $env !== null ) { 26 | putenv( 'APP_ENV=' . $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env ); 27 | } 28 | 29 | if ( $input->hasParameterOption( '--no-debug', true ) ) { 30 | putenv( 'APP_DEBUG=' . $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0' ); 31 | } 32 | 33 | ( new Dotenv() )->bootEnv( dirname( __DIR__ ) . '/.env' ); 34 | 35 | if ( $_SERVER['APP_DEBUG'] ) { 36 | umask( 0000 ); 37 | 38 | if ( class_exists( Debug::class ) ) { 39 | Debug::enable(); 40 | } 41 | } 42 | 43 | $kernel = new Kernel( $_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG'] ); 44 | $application = new Application( $kernel ); 45 | if ( class_exists( ReadmeGenCommand::class ) ) { 46 | $application->add( new ReadmeGenCommand() ); 47 | } 48 | $application->run( $input ); 49 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =8.2", 22 | "ext-ctype": "*", 23 | "ext-dom": "*", 24 | "ext-iconv": "*", 25 | "ext-intl": "*", 26 | "ext-json": "*", 27 | "ext-libxml": "*", 28 | "ext-mbstring": "*", 29 | "ext-pdo": "*", 30 | "ext-sqlite3": "*", 31 | "ext-zip": "*", 32 | "composer/package-versions-deprecated": "^1.11", 33 | "doctrine/annotations": "^2.0", 34 | "doctrine/doctrine-bundle": "^2.4", 35 | "doctrine/doctrine-migrations-bundle": "^3.1", 36 | "doctrine/orm": "^3.3", 37 | "guzzlehttp/guzzle": "^7.0", 38 | "kevinrob/guzzle-cache-middleware": "^6.0", 39 | "symfony/dotenv": "7.2.*", 40 | "symfony/framework-bundle": "7.2.*", 41 | "symfony/mailer": "7.2.*", 42 | "symfony/monolog-bundle": "^3.6", 43 | "symfony/process": "7.2.*", 44 | "symfony/stopwatch": "7.2.*", 45 | "symfony/yaml": "7.2.*", 46 | "twig/extra-bundle": "^2.12 || ^3.0", 47 | "twig/twig": "^2.12 || ^3.0", 48 | "wikimedia/html-formatter": "^4", 49 | "wikimedia/toolforge-bundle": "^1.3" 50 | }, 51 | "require-dev": { 52 | "dama/doctrine-test-bundle": "^8.2", 53 | "mediawiki/mediawiki-codesniffer": "^v46", 54 | "mediawiki/minus-x": "^1.0", 55 | "phan/phan": "^5.2", 56 | "samwilson/console-readme-generator": "^0.3", 57 | "symfony/browser-kit": "7.2.*", 58 | "symfony/css-selector": "7.2.*", 59 | "symfony/phpunit-bridge": "7.2.*", 60 | "symfony/web-profiler-bundle": "7.2.*" 61 | }, 62 | "config": { 63 | "optimize-autoloader": true, 64 | "preferred-install": { 65 | "*": "dist" 66 | }, 67 | "platform": { 68 | "php": "8.2" 69 | }, 70 | "sort-packages": true, 71 | "allow-plugins": { 72 | "dealerdirect/phpcodesniffer-composer-installer": true 73 | } 74 | }, 75 | "replace": { 76 | "paragonie/random_compat": "2.*", 77 | "symfony/polyfill-ctype": "*", 78 | "symfony/polyfill-iconv": "*", 79 | "symfony/polyfill-php72": "*", 80 | "symfony/polyfill-php71": "*", 81 | "symfony/polyfill-php70": "*", 82 | "symfony/polyfill-php56": "*" 83 | }, 84 | "scripts": { 85 | "post-install-cmd": [ 86 | "@php ./bin/console cache:clear" 87 | ], 88 | "post-update-cmd": [ 89 | "@php ./bin/console cache:clear" 90 | ], 91 | "test": [ 92 | "composer validate", 93 | "phpcs -s .", 94 | "cat bin/console | ./vendor/bin/phpcs", 95 | "@php ./bin/console lint:twig ./templates", 96 | "@php ./bin/console lint:yaml ./config", 97 | "minus-x check .", 98 | "@php ./bin/console generate-readme --include='app:' --usage='CLI Usage'", 99 | "@phan" 100 | ], 101 | "phan": [ 102 | "phan --allow-polyfill-parser --long-progress-bar --color" 103 | ], 104 | "fix": [ 105 | "phpcbf" 106 | ] 107 | }, 108 | "conflict": { 109 | "symfony/symfony": "*" 110 | }, 111 | "extra": { 112 | "symfony": { 113 | "allow-contrib": false, 114 | "require": "7.2.*" 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | [ 'all' => true ], 5 | Symfony\Bundle\TwigBundle\TwigBundle::class => [ 'all' => true ], 6 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => [ 'all' => true ], 7 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => [ 'all' => true ], 8 | Wikimedia\ToolforgeBundle\ToolforgeBundle::class => [ 'all' => true ], 9 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => [ 'dev' => true, 'test' => true ], 10 | Symfony\Bundle\MonologBundle\MonologBundle::class => [ 'all' => true ], 11 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => [ 'all' => true ], 12 | DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => [ 'test' => true ], 13 | ]; 14 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | prefix_seed: wikisource-export 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | pools: 19 | cache.replicas: 20 | adapter: cache.adapter.filesystem 21 | default_lifetime: 600 22 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | console: 9 | type: console 10 | process_psr_3_messages: false 11 | channels: ["!event", "!doctrine", "!console"] 12 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | connections: 4 | wsexport: 5 | url: '%env(resolve:DATABASE_URL)%' 6 | driver: pdo_mysql 7 | charset: utf8mb4 8 | toolforge_s1: 9 | host: '%env(REPLICAS_HOST_S1)%' 10 | port: '%env(REPLICAS_PORT_S1)%' 11 | user: '%env(REPLICAS_USERNAME)%' 12 | password: '%env(REPLICAS_PASSWORD)%' 13 | toolforge_s2: 14 | host: '%env(REPLICAS_HOST_S2)%' 15 | port: '%env(REPLICAS_PORT_S2)%' 16 | user: '%env(REPLICAS_USERNAME)%' 17 | password: '%env(REPLICAS_PASSWORD)%' 18 | toolforge_s3: 19 | host: '%env(REPLICAS_HOST_S3)%' 20 | port: '%env(REPLICAS_PORT_S3)%' 21 | user: '%env(REPLICAS_USERNAME)%' 22 | password: '%env(REPLICAS_PASSWORD)%' 23 | toolforge_s4: 24 | host: '%env(REPLICAS_HOST_S4)%' 25 | port: '%env(REPLICAS_PORT_S4)%' 26 | user: '%env(REPLICAS_USERNAME)%' 27 | password: '%env(REPLICAS_PASSWORD)%' 28 | toolforge_s5: 29 | host: '%env(REPLICAS_HOST_S5)%' 30 | port: '%env(REPLICAS_PORT_S5)%' 31 | user: '%env(REPLICAS_USERNAME)%' 32 | password: '%env(REPLICAS_PASSWORD)%' 33 | toolforge_s6: 34 | host: '%env(REPLICAS_HOST_S6)%' 35 | port: '%env(REPLICAS_PORT_S6)%' 36 | user: '%env(REPLICAS_USERNAME)%' 37 | password: '%env(REPLICAS_PASSWORD)%' 38 | toolforge_s7: 39 | host: '%env(REPLICAS_HOST_S7)%' 40 | port: '%env(REPLICAS_PORT_S7)%' 41 | user: '%env(REPLICAS_USERNAME)%' 42 | password: '%env(REPLICAS_PASSWORD)%' 43 | toolforge_s8: 44 | host: '%env(REPLICAS_HOST_S8)%' 45 | port: '%env(REPLICAS_PORT_S8)%' 46 | user: '%env(REPLICAS_USERNAME)%' 47 | password: '%env(REPLICAS_PASSWORD)%' 48 | orm: 49 | auto_generate_proxy_classes: '%kernel.debug%' 50 | default_entity_manager: default 51 | entity_managers: 52 | default: 53 | connection: wsexport 54 | mappings: 55 | Entity: 56 | type: attribute 57 | dir: '%kernel.project_dir%/src/Entity' 58 | prefix: App\Entity 59 | alias: Entity 60 | is_bundle: false 61 | controller_resolver: 62 | auto_mapping: false 63 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | #csrf_protection: true 5 | #http_method_override: true 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | session: 9 | handler_id: session.handler.native_file 10 | cookie_lifetime: 172800 11 | cookie_secure: 'auto' 12 | cookie_samesite: 'lax' 13 | save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%' 14 | storage_factory_id: session.storage.factory.native 15 | 16 | #esi: true 17 | #fragments: true 18 | php_errors: 19 | log: true 20 | -------------------------------------------------------------------------------- /config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | dsn: '%env(MAILER_DSN)%' 4 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | 3 | framework: 4 | cache: 5 | pools: 6 | doctrine.result_cache_pool: 7 | adapter: cache.app 8 | doctrine.system_cache_pool: 9 | adapter: cache.system 10 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: main_group 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | main_group: 10 | type: group 11 | members: [ nested, mailer ] 12 | nested: 13 | type: stream 14 | path: "%kernel.logs_dir%/%kernel.environment%.log" 15 | level: debug 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine"] 20 | mailer: 21 | type: deduplication 22 | time: 300 23 | handler: symfony_mailer 24 | symfony_mailer: 25 | type: symfony_mailer 26 | level: critical 27 | from_email: '%env(APP_MAIL_SENDER)%' 28 | to_email: 29 | - '%env(APP_LOG_RECIPIENT_1)%' 30 | - '%env(APP_LOG_RECIPIENT_2)%' 31 | subject: '%env(APP_LOG_SUBJECT)% %%message%%' 32 | formatter: monolog.formatter.html 33 | content_type: text/html 34 | -------------------------------------------------------------------------------- /config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | -------------------------------------------------------------------------------- /config/packages/test/dama_doctrine_test_bundle.yaml: -------------------------------------------------------------------------------- 1 | dama_doctrine_test: 2 | enable_static_connection: true 3 | enable_static_meta_data_cache: true 4 | enable_static_query_cache: true 5 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_factory_id: session.storage.factory.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /config/packages/toolforge.yaml: -------------------------------------------------------------------------------- 1 | toolforge: 2 | intuition: 3 | domain: 'ws-export' 4 | oauth: 5 | consumer_key: '%env(OAUTH_KEY)%' 6 | consumer_secret: '%env(OAUTH_SECRET)%' 7 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: attribute 4 | 5 | kernel: 6 | resource: ../../src/Kernel.php 7 | type: attribute 8 | -------------------------------------------------------------------------------- /config/routes/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@FrameworkBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /config/routes/routes.yaml: -------------------------------------------------------------------------------- 1 | toolforge: 2 | resource: '@ToolforgeBundle/Resources/config/routes.yaml' 3 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | app.tempPath: '%env(default::APP_TEMP_PATH)%' 8 | 9 | services: 10 | # default configuration for services in *this* file 11 | _defaults: 12 | autowire: true # Automatically injects dependencies in your services. 13 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 14 | 15 | doctrine.query_sql_command: 16 | class: Doctrine\DBAL\Tools\Console\Command\RunSqlCommand 17 | arguments: 18 | - '@Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider' 19 | tags: 20 | - { name: console.command, command: doctrine:query:sql } 21 | 22 | # Vendor services for autowiring 23 | GuzzleHttp\Client: 24 | GuzzleHttp\ClientInterface: '@GuzzleHttp\Client' 25 | 26 | # makes classes in src/ available to be used as services 27 | # this creates a service per class whose id is the fully-qualified class name 28 | App\: 29 | resource: '../src/' 30 | exclude: 31 | - '../src/DependencyInjection/' 32 | - '../src/Entity/' 33 | - '../src/Kernel.php' 34 | - '../src/Tests/' 35 | 36 | # controllers are imported separately to make sure services can be injected 37 | # as action arguments even if you don't extend any base controller class 38 | App\Controller\: 39 | resource: '../src/Controller/' 40 | tags: ['controller.service_arguments'] 41 | 42 | App\Controller\ExportController: 43 | arguments: 44 | $enableStats: '%env(bool:APP_ENABLE_STATS)%' 45 | $enableCache: '%env(bool:APP_ENABLE_CACHE)%' 46 | 47 | App\Command\ExportCommand: 48 | arguments: 49 | $enableCache: '%env(bool:APP_ENABLE_CACHE)%' 50 | 51 | App\EpubCheck\EpubCheck: 52 | arguments: 53 | $epubCheckPath: '%env(string:EPUBCHECK_JAR)%' 54 | public: true 55 | 56 | App\Util\Api: 57 | arguments: 58 | $cacheTtl: '%env(int:APP_CACHE_TTL)%' 59 | 60 | App\Generator\ConvertGenerator: 61 | arguments: 62 | $timeout: '%env(default::int:APP_TIMEOUT)%' 63 | 64 | App\FileCache: 65 | arguments: 66 | $projectDir: '%kernel.project_dir%' 67 | 68 | App\EventSubscriber\RateLimitSubscriber: 69 | arguments: 70 | $rateLimit: '%env(int:APP_RATE_LIMIT)%' 71 | $rateDuration: '%env(int:APP_RATE_DURATION)%' 72 | 73 | App\Util\Semaphore\Semaphore: 74 | class: 'App\Util\Semaphore\UnixSemaphore' 75 | arguments: 76 | $semaphoreKey: 123455435644 77 | $capacity: 4 78 | 79 | # add more service definitions when explicit configuration is needed 80 | # please note that last definitions always *replace* previous ones 81 | -------------------------------------------------------------------------------- /config/services_test.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | app.tempPath: '%env(default::APP_TEMP_PATH)%' 8 | 9 | services: 10 | # default configuration for services in *this* file 11 | _defaults: 12 | autowire: true # Automatically injects dependencies in your services. 13 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 14 | 15 | doctrine.query_sql_command: 16 | class: Doctrine\DBAL\Tools\Console\Command\RunSqlCommand 17 | arguments: 18 | - '@Doctrine\Bundle\DoctrineBundle\Dbal\ManagerRegistryAwareConnectionProvider' 19 | tags: 20 | - { name: console.command, command: doctrine:query:sql } 21 | 22 | # Vendor services for autowiring 23 | GuzzleHttp\Client: 24 | GuzzleHttp\ClientInterface: '@GuzzleHttp\Client' 25 | 26 | # makes classes in src/ available to be used as services 27 | # this creates a service per class whose id is the fully-qualified class name 28 | App\: 29 | resource: '../src/' 30 | exclude: 31 | - '../src/DependencyInjection/' 32 | - '../src/Entity/' 33 | - '../src/Kernel.php' 34 | - '../src/Tests/' 35 | 36 | # controllers are imported separately to make sure services can be injected 37 | # as action arguments even if you don't extend any base controller class 38 | App\Controller\: 39 | resource: '../src/Controller/' 40 | tags: ['controller.service_arguments'] 41 | 42 | App\Controller\ExportController: 43 | arguments: 44 | $enableStats: '%env(bool:APP_ENABLE_STATS)%' 45 | $enableCache: '%env(bool:APP_ENABLE_CACHE)%' 46 | 47 | App\Command\ExportCommand: 48 | arguments: 49 | $enableCache: '%env(bool:APP_ENABLE_CACHE)%' 50 | 51 | App\EpubCheck\EpubCheck: 52 | arguments: 53 | $epubCheckPath: '%env(string:EPUBCHECK_JAR)%' 54 | public: true 55 | 56 | App\Util\Api: 57 | arguments: 58 | $cacheTtl: '%env(int:APP_CACHE_TTL)%' 59 | 60 | App\Generator\ConvertGenerator: 61 | arguments: 62 | $timeout: '%env(default::int:APP_TIMEOUT)%' 63 | 64 | App\Repository\CreditRepository: 65 | public: true 66 | 67 | App\FileCache: 68 | arguments: 69 | $projectDir: '%kernel.project_dir%' 70 | 71 | App\EventSubscriber\RateLimitSubscriber: 72 | arguments: 73 | $rateLimit: '%env(int:APP_RATE_LIMIT)%' 74 | $rateDuration: '%env(int:APP_RATE_DURATION)%' 75 | 76 | # add more service definitions when explicit configuration is needed 77 | # please note that last definitions always *replace* previous ones 78 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3-apache 2 | 3 | ARG USER_ID 4 | ARG GROUP_ID 5 | 6 | WORKDIR /var/www/html 7 | 8 | EXPOSE 8080 9 | 10 | # work around https://github.com/docker-library/openjdk/blob/0584b2804ed12dca7c5e264b5fc55fc07a3ac148/8-jre/slim/Dockerfile#L51-L54 11 | RUN mkdir -p /usr/share/man/man1 12 | 13 | RUN addgroup --gid $GROUP_ID docker 14 | RUN adduser --disabled-password --gecos '' --uid $USER_ID --gid $GROUP_ID docker 15 | 16 | RUN apt-get update -q && apt-get install -y \ 17 | python \ 18 | libnss3 \ 19 | libegl1 \ 20 | libopengl0 \ 21 | jarwrapper \ 22 | libcommons-compress-java \ 23 | libguava-java \ 24 | libjackson-json-java \ 25 | libjing-java \ 26 | libsac-java \ 27 | libsaxonhe-java \ 28 | fontconfig \ 29 | fonts-gujr-extra \ 30 | fonts-freefont-ttf \ 31 | fonts-linuxlibertine \ 32 | fonts-dejavu-core \ 33 | fonts-gubbi \ 34 | fonts-opendyslexic \ 35 | git \ 36 | libzip-dev \ 37 | libicu-dev \ 38 | unzip \ 39 | wget \ 40 | xdg-utils \ 41 | xz-utils \ 42 | && pecl install xdebug zip \ 43 | && docker-php-ext-enable xdebug zip \ 44 | && docker-php-ext-install pdo_mysql intl \ 45 | && a2enmod rewrite \ 46 | && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin \ 47 | && wget https://github.com/w3c/epubcheck/releases/download/v4.2.4/epubcheck-4.2.4.zip \ 48 | && unzip epubcheck-4.2.4.zip \ 49 | && mv epubcheck-4.2.4 /usr/bin/. \ 50 | && echo 'java -jar /usr/bin/epubcheck-4.2.4/epubcheck.jar "$@"' > /usr/bin/epubcheck \ 51 | && chmod a+x /usr/bin/epubcheck \ 52 | && wget -nv -O- https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ 53 | && wget -nv -O- https://get.symfony.com/cli/installer | bash \ 54 | && mv /root/.symfony/bin/symfony /usr/local/bin/symfony 55 | 56 | USER docker 57 | 58 | -------------------------------------------------------------------------------- /docker/apache/000-default.conf: -------------------------------------------------------------------------------- 1 | 2 | # The ServerName directive sets the request scheme, hostname and port that 3 | # the server uses to identify itself. This is used when creating 4 | # redirection URLs. In the context of virtual hosts, the ServerName 5 | # specifies what hostname must appear in the request's Host: header to 6 | # match this virtual host. For the default virtual host (this file) this 7 | # value is not decisive as it is used as a last resort host regardless. 8 | # However, you must set it for any further virtual host explicitly. 9 | #ServerName www.example.com 10 | 11 | ServerAdmin webmaster@localhost 12 | DocumentRoot /var/www/html/public 13 | DirectoryIndex index.php 14 | 15 | 16 | RewriteEngine On 17 | RewriteCond %{REQUEST_FILENAME} !-f 18 | RewriteRule . index.php [QSA] 19 | 20 | 21 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 22 | # error, crit, alert, emerg. 23 | # It is also possible to configure the loglevel for particular 24 | # modules, e.g. 25 | #LogLevel info ssl:warn 26 | 27 | ErrorLog ${APACHE_LOG_DIR}/error.log 28 | CustomLog ${APACHE_LOG_DIR}/access.log combined 29 | 30 | # For most configuration files from conf-available/, which are 31 | # enabled or disabled at a global level, it is possible to 32 | # include a line for only one particular virtual host. For example the 33 | # following line enables the CGI configuration for this host only 34 | # after it has been globally disabled with "a2disconf". 35 | #Include conf-available/serve-cgi-bin.conf 36 | 37 | 38 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 39 | -------------------------------------------------------------------------------- /docker/apache/ports.conf: -------------------------------------------------------------------------------- 1 | # If you just change the port or add more ports here, you will likely also 2 | # have to change the VirtualHost statement in 3 | # /etc/apache2/sites-enabled/000-default.conf 4 | 5 | Listen 8080 6 | 7 | 8 | Listen 443 9 | 10 | 11 | 12 | Listen 443 13 | 14 | 15 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 16 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | wsexport: 4 | build: 5 | context: . 6 | args: 7 | USER_ID: ${WS_DOCKER_UID} 8 | GROUP_ID: ${WS_DOCKER_GID} 9 | ports: 10 | - "${WS_EXPORT_PORT:-8888}:8080" 11 | extra_hosts: 12 | - "host.docker.internal:host-gateway" 13 | volumes: 14 | - ../:/var/www/html:cached 15 | - ./apache/000-default.conf:/etc/apache2/sites-available/000-default.conf:cached 16 | - ./apache/ports.conf:/etc/apache2/ports.conf:cached 17 | - ./php.ini:/usr/local/etc/php/php.ini:cached 18 | links: 19 | - database:database 20 | environment: 21 | - APACHE_RUN_USER=daemon 22 | - XDEBUG_MODE=off 23 | depends_on: 24 | - database 25 | database: 26 | image: mariadb 27 | restart: always 28 | environment: 29 | - MYSQL_ALLOW_EMPTY_PASSWORD=1 30 | - MYSQL_DATABASE=wsexport 31 | -------------------------------------------------------------------------------- /docker/php.ini: -------------------------------------------------------------------------------- 1 | [xdebug] 2 | xdebug.client_host=host.docker.internal 3 | xdebug.client_port=9000 4 | xdebug.start_with_request=yes 5 | xdebug.discover_client_host=1 6 | -------------------------------------------------------------------------------- /i18n/br.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Adriendelucca", 5 | "Huñvreüs" 6 | ] 7 | }, 8 | "footer-version": "Hemañ zo $1 stumm $2.", 9 | "version": "stumm $1", 10 | "license-link": "GPL v2 pe diwezhatoc'h", 11 | "source-link": "war Github", 12 | "docs-link": "war Wikimammenn liesyezhek", 13 | "statistics-link": "stadegoù", 14 | "back-to-wikisource": "Distreiñ da Wikimammenn", 15 | "export-book": "Ezporzhiañ ul levr", 16 | "lang-field-label": "Wikimammenn", 17 | "title-field-label": "Titl", 18 | "format-field-label": "Furmad ar restr", 19 | "format-htmlz": "HTML", 20 | "format-pdf-a4": "PDF - Ment A4", 21 | "format-pdf-a5": "PDF - Ment A5", 22 | "format-pdf-a6": "PDF - Ment A6", 23 | "font-field-label": "Font", 24 | "no-font-option": "Hini ebet (ober gant an hini dre ziouer)", 25 | "options-label": "Dibarzhioù", 26 | "export-button": "Ezporzhiañ", 27 | "alert-close": "Serriñ", 28 | "export-failed": "Pellgargañ c'hwitet.", 29 | "exception-rest-page-not-found": "N'haller ket kavout al levr \"$1\"", 30 | "learn-more": "Gouzout hiroc'h.", 31 | "error-breadcrumb": "Fazi $1", 32 | "error-page-header": "Ur fazi zo bet", 33 | "epub-title-page": "Titl ar bajenn", 34 | "epub-exported-date": "Ezporzhiet diouzh Wikimammenn war $1", 35 | "epub-about": "Diwar-benn" 36 | } 37 | -------------------------------------------------------------------------------- /i18n/ce.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Умар" 5 | ] 6 | }, 7 | "app-title": "Викихьостан чуьра жайнаш экспорт дар", 8 | "app-title-html": "$1 чуьра жайнаш экспорт дар", 9 | "app-subtitle": "Тайп-тайпанчу файлан форматашца Викихьостера жайнаш экспорт дар", 10 | "logo-alt-text": "Викихьостан логотип, айсберган сийна а, кӀайн а сурт ду.", 11 | "footer-version": "ХӀара $1 верси йу $2.", 12 | "version": "$1 верси", 13 | "commit-label": "Коммит: $1", 14 | "license-link": "GPL v2 йа тӀаьхьо", 15 | "source-link": "Github тӀехь", 16 | "title-field-label": "Корта", 17 | "format-pdf-a4": "PDF — А4 барам", 18 | "format-pdf-a5": "PDF — A5 барам", 19 | "format-pdf-a6": "PDF — A6 барам", 20 | "font-field-label": "Шрифт", 21 | "options-label": "Нисдаран гӀирс", 22 | "export-button": "Экспортйе", 23 | "alert-close": "ДӀачӀагӀа", 24 | "learn-more": "Цул совнаха хаа.", 25 | "error-blocked-1": "ХӀинца йолу ханна хьан IP-адрес блоктоьхна ду.", 26 | "error-breadcrumb": "$1 гӀалат", 27 | "error-page-header": "ГӀалат даьлла", 28 | "error-page-details": "ГӀалатах информаци: $1", 29 | "epub-title-page": "Корта", 30 | "epub-about": "Информаци", 31 | "breadcrumbs-home-link": "Коьрта", 32 | "breadcrumbs-stats-link": "Статистика", 33 | "recently-popular-heading": "ТӀаьхьарчу хенахь гӀарайаьлла", 34 | "recently-popular-subtext": "ТӀаьххьарчу кхаа баттахь уггаре а дукха чуйаьхнарш" 35 | } 36 | -------------------------------------------------------------------------------- /i18n/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Reaperman" 5 | ] 6 | }, 7 | "app-title": "Export knih z Wikizdrojů", 8 | "app-title-wikisource": "Wikizdroje", 9 | "app-subtitle": "Exportujte knihy z Wikizdrojů v mnoha různých formátech.", 10 | "logo-alt-text": "Logo Wikizdrojů, modrobílá ilustrace znázorňující ledovec.", 11 | "back-to-wikisource": "Zpět na Wikizdroje", 12 | "export-book": "Exportovat knihu", 13 | "lang-field-label": "Wikizdroje", 14 | "lang-field-help": "Wikizdroje, ze kterých se má kniha exportovat.", 15 | "title-field-label": "Název", 16 | "title-field-help": "Název knihy nebo části k exportu.", 17 | "format-field-label": "Formát souboru", 18 | "format-epub-3": "EPUB 3 (vhodný pro většinu čteček)", 19 | "format-epub-2": "EPUB 2 (zastaralý, může být užitečný pro některé velmi staré čtečky)", 20 | "format-htmlz": "HTML", 21 | "format-mobi": "MOBI (čtečky Kindle)", 22 | "format-pdf-a4": "PDF (formát A4)", 23 | "format-pdf-a5": "PDF (formát A5)", 24 | "format-pdf-a6": "PDF (formát A6)", 25 | "format-pdf-letter": "PDF (formát US letter)", 26 | "format-rtf": "RTF", 27 | "format-txt": "Prostý text", 28 | "exceeded-rate-limitation": "Zadáváte příliš mnoho požadavků během krátké doby. Vyčkejte prosím $1 {{PLURAL:$1|minutu|minuty|minut}} před opětovným načtením tohoto nástroje nebo se přihlaste.", 29 | "exception-invalid-format": "\" $1 \" není platný formát. Platné formáty jsou: $2", 30 | "font-field-label": "Písmo", 31 | "no-font-option": "Žádné (použít výchozí písmo čtečky)", 32 | "font-field-help": "Vyberte si z $1 dostupných písem.", 33 | "options-label": "Možnosti", 34 | "credits-field-label": "Nezahrnovat seznam editorů (pro rychlejší stažení)", 35 | "credits-default-message": "[Seznam přispěvatelů byl podle zadání vynechán.]", 36 | "images-field-label": "Nezahrnovat obrázky", 37 | "nocache-field-label": "Obejít mezipaměť (pomalejší, ale užitečné pro ladění)", 38 | "export-button": "Exportovat", 39 | "cache-updated": "Mezipaměť byla obnovena pro jazyk: $1", 40 | "alert-close": "Zavřít", 41 | "export-failed": "Stažení se nezdařilo.", 42 | "learn-more": "Více informací", 43 | "error-blocked-1": "Vaše IP adresa je aktuálně blokována.", 44 | "epub-title-page": "Titulní strana", 45 | "epub-exported-date": "Exportováno z Wikizdrojů $1", 46 | "epub-about": "O projektu" 47 | } 48 | -------------------------------------------------------------------------------- /i18n/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Peter Alberti", 5 | "Saederup92" 6 | ] 7 | }, 8 | "app-title": "Wikisource Bogeksport", 9 | "app-subtitle": "Eksporter bøger fra Wikisource i mange forskellige filformater", 10 | "licensed-under": "Den er tilgængelig under licensen $1.", 11 | "license-link": "GPL v2 eller senere", 12 | "source-link": "på Github", 13 | "statistics": "En hvis $1 er også tilgængelig.", 14 | "statistics-link": "statistik", 15 | "back-to-wikisource": "Tilbage til Wikisource", 16 | "export-book": "Eksporter en bog", 17 | "lang-field-label": "Wikisource", 18 | "lang-field-help": "Den Wikisource, som bogen skal eksporteres fra.", 19 | "title-field-label": "Titel", 20 | "title-field-help": "Titlen på bogens forside.", 21 | "format-field-label": "Filformat", 22 | "format-epub-3": "EPUB 3 (til de fleste e-bogslæsere)", 23 | "format-epub-2": "EPUB 2 (forældet, kan være brugbar med visse, ældre e-bogslæsere)", 24 | "format-htmlz": "HTML", 25 | "format-mobi": "MOBI (til Kindle)", 26 | "format-pdf-a4": "PDF - A4-størrelse", 27 | "format-pdf-a5": "PDF - A5-størrelse", 28 | "format-pdf-a6": "PDF - A6-størrelse", 29 | "format-pdf-letter": "PDF - amerikansk letterstørrelse", 30 | "format-rtf": "RTF", 31 | "format-txt": "Uformateret tekst", 32 | "exception-invalid-format": "\"$1\" er ikke et gyldigt format. De gyldige formater er: $2", 33 | "font-field-label": "Skrifttype", 34 | "no-font-option": "Ingen (anvend enhedens standard)", 35 | "font-field-help": "Vælg mellem $1 tilgængelige skrifttyper.", 36 | "options-label": "Valgmuligheder", 37 | "images-field-label": "Medtag ikke billeder", 38 | "nocache-field-label": "Ignorer alle mellemlagre (langsommere, men brugbar ved fejlsøgning)", 39 | "export-button": "Eksportér", 40 | "alert-close": "Luk", 41 | "learn-more": "Lær mere.", 42 | "error-page-header": "Der opstod en fejl", 43 | "epub-title-page": "Titel", 44 | "epub-exported-date": "Eksporteret fra Wikisource den $1", 45 | "epub-about": "Om", 46 | "breadcrumbs-home-link": "Hjem", 47 | "breadcrumbs-stats-link": "Statistik", 48 | "change-lang": "Skift sprog" 49 | } 50 | -------------------------------------------------------------------------------- /i18n/dag.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Achiri Bitamsimli", 5 | "Alhassan Peter", 6 | "Anamzooya", 7 | "Chirifo", 8 | "Hidrash", 9 | "Mo Yumzaa", 10 | "Ruky Wunpini", 11 | "Sir Amugi" 12 | ] 13 | }, 14 | "app-title": "Wikisɔɔsi buku yɛrili", 15 | "app-title-html": "$1 buku yihi tahi shɛli polo", 16 | "logo-alt-text": "Wikisɔɔsi dalinli nyɛ zaɣa nɔɔso ni zaɣa pɛɣli buɣisirili", 17 | "back-to-wikisource": "Labimi wikisɔɔsi", 18 | "previous-alt-text": "Kpani din tuhi nyangi ŋɔ wuhirila nyan labbo", 19 | "lang-field-label": "Wikisɔɔsi", 20 | "title-field-label": "Yɛlizuɣu", 21 | "title-field-help": "Buku yuli", 22 | "format-field-label": "Faali biɛhigu", 23 | "font-field-label": "Bachikoba barilim", 24 | "options-label": "Piibu", 25 | "credits-field-label": "Gilma malza yihiya", 26 | "credits-default-message": "Yushaŋa din zaaha pahi ma yiya bi ni borili sham", 27 | "alert-close": " N Kpihi bee n kpari", 28 | "export-failed": "Yaa bu saɣ siya.", 29 | "exception-url-fetch-error": "M-bi tooi lahi bɔ n-nya URL: $1", 30 | "exception-rest-page-not-found": "$1 book laa ka ni", 31 | "learn-more": "Bohimmi pam", 32 | "error-fix": "Pahimi Suɣlo Ka doli soya ŋɔ diyi ŋin Ka a Mali muɣsigu", 33 | "error-fix-again": "Paɣmi a maŋa n lab waa bulk maa.", 34 | "error-fix-epub": "Kpaŋmi maŋ n-yihi boku koligu mani", 35 | "error-fix-nocredits": "Yulimi kpale", 36 | "error-fix-noimages": "Yulimi adaka maa ni ' di mali piibu' n-yihi anfooninima.", 37 | "error-breadcrumb": "Chiriŋ $1", 38 | "error-page-header": "Muɣisigu beni", 39 | "error-page-details": "Chirimbu bayaana:$1", 40 | "error-page-issue": "Chirimbu ŋɔ yi kuli zaɣisi, pahili suɣilo $1", 41 | "error-page-issue-link": "Yihi yali yalli yalmaŋli kalinsi", 42 | "onwikiconfig-failure": "Bi tooi deei", 43 | "exception-book-conversion": "Chiriŋ kana buku maa biɛhigu taɣibu ni.", 44 | "epub-title-page": "Gbaŋ ŋmari yaɣili", 45 | "epub-exported-date": "Wikisɔɔsi $1 puuuni ka di yi n-chaŋ shɛli polo", 46 | "epub-about": "Din nye shem" 47 | } 48 | -------------------------------------------------------------------------------- /i18n/diq.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "GolyatGeri", 5 | "Mirzali" 6 | ] 7 | }, 8 | "app-title": "Wikiçıme kıtab teberdayış", 9 | "app-title-html": "$1 Kıtab Teberdayış", 10 | "version": "versiyon $1", 11 | "commit-label": "Cıfi: $1", 12 | "licensed-under": "Pê $1 ya lisans biya", 13 | "license-link": "GPL v2 ya ki serên", 14 | "source-link": "Github de", 15 | "docs-link": "Zafzonın Wikiçıme", 16 | "statistics-link": "istatistiki", 17 | "issue-button": "mesel vıraze", 18 | "back-to-wikisource": "Peyser so Wikiçıme", 19 | "export-book": "Yo kıtabi teberkerdış", 20 | "lang-field-label": "Wikiçıme", 21 | "title-field-label": "Serek:", 22 | "format-field-label": "Formatê dosya", 23 | "format-epub-3": "EPUB 3 (zeder serva e-wanayoğ)", 24 | "format-htmlz": "HTML", 25 | "format-mobi": "MOBI (Kindles)", 26 | "format-pdf-a4": "PDF - A4 size", 27 | "format-pdf-a5": "PDF - A5 size", 28 | "format-pdf-a6": "PDF - A6 size", 29 | "format-pdf-letter": "PDF - US mektub", 30 | "format-rtf": "RTF", 31 | "format-txt": "Metno pehan", 32 | "font-field-label": "Tipê nusni", 33 | "no-font-option": "Çıno (hesabyaye cihazi bıgereyne)", 34 | "options-label": "Weçinıteki", 35 | "credits-field-label": "İştirakgera demefi (pêta robeno)", 36 | "export-button": "Teberdayış", 37 | "cache-updated": "Serva verviri zonê bı newe :$1", 38 | "alert-close": "Kefelnayış", 39 | "export-failed": "Barkerdış nêbi.", 40 | "learn-more": "Tayêna bımuse", 41 | "error-blocked-1": "Adresa IP enewke men biya.", 42 | "error-page-header": "Yew xeta biye", 43 | "error-page-details": "Detay xetay:$1", 44 | "error-page-issue": "Ek na xeta dewam kena se, kerem kerê $1", 45 | "error-page-issue-link": "Phabricator'da Yo mesel gere kerên", 46 | "epub-title-page": "Perla sereki", 47 | "epub-exported-date": "$1 Wikiçıme ra deyaya teber", 48 | "epub-about": "Heqa", 49 | "breadcrumbs-home-link": "Çeye", 50 | "breadcrumbs-stats-link": "İstatistiki", 51 | "recently-popular-heading": "Tewr populer" 52 | } 53 | -------------------------------------------------------------------------------- /i18n/eo.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Robin van der Vliet" 5 | ] 6 | }, 7 | "version": "versio $1", 8 | "commit-label": "Enmeto: $1", 9 | "licensed-under": "Ĝi estas eldonita sub la permesilo $1.", 10 | "statistics-link": "statistikoj", 11 | "lang-field-label": "Vikifontaro", 12 | "title-field-label": "Titolo", 13 | "format-field-label": "Dosieraranĝo", 14 | "format-htmlz": "HTML", 15 | "format-mobi": "MOBI (por Kindles)", 16 | "format-rtf": "RTF", 17 | "format-txt": "Plata teksto", 18 | "font-field-label": "Tiparo", 19 | "options-label": "Opcioj", 20 | "export-button": "Eksporti", 21 | "alert-close": "Fermi", 22 | "error-breadcrumb": "Eraro $1", 23 | "error-page-header": "Eraro okazis", 24 | "epub-title-page": "Titolpaĝo", 25 | "epub-exported-date": "Elŝutita el Vikifontaro la $1", 26 | "epub-about": "Pri ĉi tiu eldonaĵo" 27 | } 28 | -------------------------------------------------------------------------------- /i18n/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Pikne" 5 | ] 6 | }, 7 | "app-title": "Vikitekstide raamatu eksport", 8 | "app-title-wikisource": "Vikitekstide", 9 | "app-title-html": "$1 raamatu eksport", 10 | "app-subtitle": "Raamatute eksportimine Vikitekstidest paljudes failivormingutes.", 11 | "footer-version": "See on tööriista $1 $2.", 12 | "version": "versioon $1", 13 | "commit-label": "Panus: $1", 14 | "licensed-under": "See kuulub litsentsi $1 alla.", 15 | "license-link": "GPL v2 või hilisem", 16 | "source-and-docs": "Lähtekood on saadaval $1 ja osa dokumentatsioonist $2.", 17 | "source-link": "Githubis", 18 | "docs-link": "mitmekeelsetes Vikitekstides", 19 | "statistics": "Ära on toodud ka mõned $1.", 20 | "statistics-link": "arvandmed", 21 | "issues": "Palun anna probleemidest teada Phabricatori projektis $1.", 22 | "issue-button": "Koosta teade", 23 | "back-to-wikisource": "Tagasi Vikitekstidesse", 24 | "export-book": "Raamatu eksportimine", 25 | "lang-field-label": "Vikitekstid", 26 | "lang-field-help": "Vikitekstid, kust raamat eksportida.", 27 | "title-field-label": "Pealkiri", 28 | "title-field-help": "Raamatu esilehe pealkiri.", 29 | "format-field-label": "Failivorming", 30 | "format-epub-3": "EPUB 3 (enamikus e-lugerites)", 31 | "format-epub-2": "EPUB 2 (vananenud, võib sobida kasutamiseks mõnes väga vanas e-lugeris)", 32 | "format-mobi": "MOBI (lugeris Kindle)", 33 | "format-pdf-a4": "PDF – A4-suurus", 34 | "format-pdf-a5": "PDF – A5-suurus", 35 | "format-pdf-a6": "PDF – A6-suurus", 36 | "format-pdf-letter": "PDF – USA suurus Letter", 37 | "format-txt": "Lihttekst", 38 | "exception-invalid-format": "\"$1\" ei ole sobiv vorming. Sobivad vormingud: $2", 39 | "font-field-label": "Kirjatüüp", 40 | "no-font-option": "Puudub (kasuta seadme vaikeväärtust)", 41 | "font-field-help": "Vali $1 saadaoleva kirjatüübi seast.", 42 | "options-label": "Suvandid", 43 | "credits-field-label": "Jäta toimetajate andmed välja (kiirem allalaadimine)", 44 | "credits-default-message": "[Kaastööliste nimekiri vastavalt päringule välja jäetud.]", 45 | "images-field-label": "Jäta pildid välja", 46 | "nocache-field-label": "Alista kõik puhvrid (aeglasem, aga kasulik silumiseks)", 47 | "export-button": "Ekspordi", 48 | "cache-updated": "Järgmise keele puhver värskendatud: $1" 49 | } 50 | -------------------------------------------------------------------------------- /i18n/eu.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Atzerritik", 5 | "Theklan" 6 | ] 7 | }, 8 | "app-title": "Wikisource Book Export", 9 | "app-title-html": "$1 liburu esportatu", 10 | "app-subtitle": "Esportatu Wikisources-etik liburuak fitxategi formatu ezberdinetan.", 11 | "logo-alt-text": "Wikisource logotipoa, iceberg baten ilustrazio zuri urdina.", 12 | "footer-version": "Hau $1 $2 bertsioa da.", 13 | "version": "$1 bertsioa", 14 | "licensed-under": "$1 lizenziapean egina.", 15 | "license-link": "GPL v2 edo berriagoa", 16 | "source-link": "Github-en", 17 | "docs-link": "Hizkuntza-anitzeko Wikitekan", 18 | "statistics": "$1 batzuk ere ematen dira", 19 | "statistics-link": "estatistika", 20 | "issue-button": "Sortu arazoa", 21 | "back-to-wikisource": "Itzuli Wikisourcera", 22 | "previous-alt-text": "Esteka Wikisourcera itzultzen dela adierazten duen atzerantz begira dagoen gezi-ikonoa.", 23 | "export-book": "Esportatu liburu bat", 24 | "lang-field-label": "Wikiteka", 25 | "lang-field-help": "Liburua esportatzeko Wikiteka.", 26 | "title-field-label": "Izenburua", 27 | "format-field-label": "Fitxategi formatuak", 28 | "format-epub-3": "EPUB 3 (irakurle gehienentzat)", 29 | "format-epub-2": "EPUB 2 (zaharkituta, erabilgarria izan daiteke irakurle zahar batzuentzat)", 30 | "format-htmlz": "HTML", 31 | "format-mobi": "MOBI (Kindleerako)", 32 | "format-pdf-a4": "PDF - A4 tamaina", 33 | "format-pdf-a5": "PDF - A5 tamaina", 34 | "format-pdf-a6": "PDF - A6 tamaina", 35 | "format-pdf-letter": "PDF - AEBetako letra tamaina", 36 | "format-rtf": "RTF", 37 | "format-txt": "Testu laua", 38 | "exceeded-rate-limitation": "Eskaera gehiegi egiten ari zara denbora tarte laburrean. Mesedez, itxaron $1 {{PLURAL:$1| minutu|minutu}} tresna hau berriro kargatu baino lehen, edo hasi saioa berriro gerta ez dadin.", 39 | "exception-invalid-format": "\" $1 \" ez da baliozko formatua. Baliozko formatuak hauek dira: $2", 40 | "font-field-label": "Letra-tipoa", 41 | "options-label": "Aukerak", 42 | "images-field-label": "Ez sartu irudirik", 43 | "nocache-field-label": "Cache guztiak ahaztu (geldoagoa da, baina arazoak aurkitzeko hobea)", 44 | "export-button": "Esportatu", 45 | "cache-updated": "Hizkuntza honetarako cachea freskatu da: $1", 46 | "alert-close": "Itxi", 47 | "export-failed": "Jaitsieran akatsa egon da.", 48 | "exception-url-fetch-error": "Ezin izan da $1 URLa eskuratu", 49 | "exception-rest-page-not-found": "'$1' orrialdea ezin da aurkitu.", 50 | "learn-more": "Gehiago ikasi.", 51 | "error-fix-again": "Saia zaitez liburua berriro jaisten.", 52 | "error-fix-epub": "Saia zaitez liburua EPUB fitxategi gisa jaisten.", 53 | "error-breadcrumb": "$1 akatsa", 54 | "error-page-header": "Akats bat gertatu da", 55 | "error-page-details": "Akatsaren xehetasunak: $1", 56 | "error-page-issue": "Akatsak irauten badu, $1 mesedez", 57 | "error-page-issue-link": "akatsaren berri eman Phabricatorren", 58 | "epub-title-page": "Azaleko orrialdea" 59 | } 60 | -------------------------------------------------------------------------------- /i18n/fa.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Darafsh", 5 | "Jeeputer", 6 | "Yoosef Pooranvary" 7 | ] 8 | }, 9 | "app-title": "کتاب‌ساز ویکی‌نبشته", 10 | "app-title-html": "کتاب‌ساز $1", 11 | "app-subtitle": "برون‌بری کتاب‌ها از ویکی‌نبشته به صورت فرمت‌های متعدد و مختلف.", 12 | "logo-alt-text": "لوگوی ویکی‌نبشته، کوه یخی به رنگ سفید و آبی.", 13 | "footer-version": "این نسخهٔ $1 از $2 است.", 14 | "version": "نسخهٔ $1", 15 | "commit-label": "تعهد: $1", 16 | "licensed-under": "تحت مجوز $1 است.", 17 | "license-link": "GPL v2 یا جدیدتر", 18 | "source-and-docs": "کد منبع در $1 و برخی اسناد در $2 موجودند.", 19 | "source-link": "در گیت‌هاب", 20 | "docs-link": "در ویکی‌نبشتهٔ چندزبانه", 21 | "format-htmlz": "HTML", 22 | "format-mobi": "MOBI (برای کیندل)", 23 | "format-pdf-a4": "PDF - اندازهٔ A4", 24 | "format-pdf-a5": "PDF - اندازهٔ A5", 25 | "format-pdf-a6": "PDF - اندازهٔ A6", 26 | "format-pdf-letter": "PDF - اندازهٔ نامه", 27 | "format-rtf": "RTF", 28 | "format-txt": "متن ساده", 29 | "font-field-label": "قلم", 30 | "no-font-option": "هیچکدام (استفاده از پیش‌فرض دستگاه)", 31 | "font-field-help": "از میان $1 قلم موجود انتخاب کنید.", 32 | "options-label": "گزینه‌ها", 33 | "credits-field-label": "انتساب‌ها به ویرایشگران را مستثنا کن (بارگیری سریع‌تر)", 34 | "credits-default-message": "[فهرست مشارکت‌کنندگان بنا به درخواست حذف شده است.]", 35 | "images-field-label": "تصاویر را وارد نکن", 36 | "nocache-field-label": "از تمام کش‌ها عبور کن (کندتر اما مفید برای اشکال‌زدایی)", 37 | "export-button": "برون‌بری", 38 | "alert-close": "بستن", 39 | "exception-rest-page-not-found": "ناتوان در یافتن صفحهٔ «$1».", 40 | "epub-title-page": "عنوان صفحه", 41 | "epub-exported-date": "خروجی ویکی‌نبشته", 42 | "epub-about": "دربارهٔ", 43 | "breadcrumbs-home-link": "خانه", 44 | "breadcrumbs-stats-link": "آمار", 45 | "recently-popular-heading": "اخیراً محبوب", 46 | "recently-popular-subtext": "محبوب ترین دانلودها در سه ماه گذشته", 47 | "change-lang": "تغییر زبان" 48 | } 49 | -------------------------------------------------------------------------------- /i18n/fi.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Joquliina", 5 | "Mediawikitranslator", 6 | "Pyscowicz" 7 | ] 8 | }, 9 | "app-title": "Wikiaineisto-kirjan vienti", 10 | "app-title-wikisource": "Wikiaineisto", 11 | "app-title-html": "$1-kirjan vienti", 12 | "app-subtitle": "Vie kirjoja Wikiaineistosta useissa eri tiedostomuodoissa.", 13 | "logo-alt-text": "Wikiaineisto-logo, sinivalkoinen kuva jäävuoresta.", 14 | "footer-version": "Tämä on $1, versio $2 .", 15 | "version": "versio $1", 16 | "licensed-under": "Se on lisensoitu $1.", 17 | "license-link": "GPL v2 -lisenssillä tai myöhemmällä", 18 | "source-and-docs": "Lähdekoodi on saatavilla $1 ja osa dokumentaatiosta $2 .", 19 | "source-link": "Githubissa", 20 | "docs-link": "monikielisessä Wikiaineistossa", 21 | "statistics": "Myös joitain $1 tarjotaan.", 22 | "statistics-link": "tilastoja", 23 | "issue-button": "Luo issue", 24 | "back-to-wikisource": "Takaisin Wikiaineistoon", 25 | "export-book": "Vie kirja", 26 | "lang-field-label": "Wikiaineisto", 27 | "lang-field-help": "Wikiaineisto, josta kirja viedään.", 28 | "title-field-label": "Otsikko", 29 | "title-field-help": "Vietävän kirjan tai osion nimi.", 30 | "format-field-label": "Tiedostomuoto", 31 | "format-epub-3": "EPUB 3 (useimmille lukulaitteille)", 32 | "format-epub-2": "EPUB 2 (vanhentunut, saattaa olla hyödyllinen joillekin hyvin vanhoille lukulaitteille)", 33 | "format-htmlz": "HTML", 34 | "format-mobi": "MOBI (Kindleille)", 35 | "format-pdf-a4": "PDF - A4 koko", 36 | "format-pdf-a5": "PDF - A5 koko", 37 | "format-pdf-a6": "PDF - A6 koko", 38 | "format-pdf-letter": "PDF - US letter koko", 39 | "format-rtf": "RTF", 40 | "format-txt": "Pelkkä teksti", 41 | "exception-invalid-format": "\"$1\" ei ole kelvollinen muoto. Kelvolliset muodot ovat: $2", 42 | "font-field-label": "Kirjaisin", 43 | "no-font-option": "Ei mitään (käytä laitteen oletusarvoa)", 44 | "font-field-help": "Valitse $1 käytettävissä olevasta fontista.", 45 | "options-label": "Asetukset", 46 | "images-field-label": "Älä sisällytä kuvia", 47 | "nocache-field-label": "Ohita kaikki välimuisti (hitaampi, mutta hyödyllinen virheenkorjauksessa)", 48 | "export-button": "Vie", 49 | "cache-updated": "Välimuisti on päivitetty kielelle: $1", 50 | "alert-close": "Sulje", 51 | "export-failed": "Lataaminen epäonnistui.", 52 | "exception-rest-page-not-found": "Sivua '$1' ei löytynyt.", 53 | "learn-more": "Lisätietoja", 54 | "error-blocked-1": "IP-osoitteesi on tällä hetkellä estetty.", 55 | "error-fix": "Kokeile näitä ohjeita, jos kohtaat virheen:", 56 | "error-fix-again": "Yritä ladata kirja uudelleen.", 57 | "error-fix-epub": "Yritä ladata kirja EPUB-tiedostona.", 58 | "error-breadcrumb": "Virhe $1", 59 | "error-login": "Kirjaudu sisään WS Exportiin jatkaaksesi.", 60 | "error-page-header": "Tapahtui virhe", 61 | "error-page-details": "Virhetiedot: $1", 62 | "error-page-issue": "Jos virhe jatkuu, $1.", 63 | "error-page-issue-link": "ilmoita ongelmasta Phabricatorissa", 64 | "exception-book-conversion": "Virhe muunnettaessa kirjan muotoa.", 65 | "epub-exported-date": "Viety Wikiaineistosta $1", 66 | "epub-about": "Tietoja", 67 | "breadcrumbs-home-link": "Etusivu", 68 | "breadcrumbs-stats-link": "Tilastot", 69 | "recently-popular-heading": "Viime aikoina suosittu" 70 | } 71 | -------------------------------------------------------------------------------- /i18n/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Amire80", 5 | "YaronSh" 6 | ] 7 | }, 8 | "app-title": "ייצוא ספר מוויקיטקסט", 9 | "app-title-wikisource": "ויקיטקסט", 10 | "app-title-html": "ייצוא הספר $1", 11 | "app-subtitle": "יצוא ספרים מוויקיטקסטים במגוון תצורות קבצים שונות.", 12 | "logo-alt-text": "הלוגו של ויקיטקסט, איור של קרחון בכחול ולבן.", 13 | "footer-version": "זה $1 בגרסה $2.", 14 | "version": "גרסה $1", 15 | "commit-label": "הגשה: $1", 16 | "licensed-under": "מוגש ברישיון $1.", 17 | "license-link": "GPL v2 ומעלה", 18 | "source-and-docs": "קוד המקור זמין $1 וחלק מהתיעוד $2.", 19 | "source-link": "ב־Github", 20 | "docs-link": "בוויקיטקסט רב־לשוני", 21 | "statistics": "חלק מה$1 מסופקת בנוסף.", 22 | "statistics-link": "סטטיסטיקה", 23 | "issues": "נא לדווח על תקלות תחת המיזם $1 ב־Phabricator.", 24 | "issue-button": "יצירת בקשה", 25 | "back-to-wikisource": "חזרה לוויקיטקסט", 26 | "previous-alt-text": "חץ שפונה לכיוון ההפוך שמציין שהקישור הזה חוזר לוויקיטקסט.", 27 | "export-book": "לייצא ספר", 28 | "lang-field-label": "ויקיטקסט", 29 | "lang-field-help": "הוויקיטקסט שהספר ייוצא ממנו", 30 | "title-field-label": "כותרת", 31 | "title-field-help": "כותרת העמוד הראשי של הספר.", 32 | "format-field-label": "סוג קובץ", 33 | "format-epub-3": "EPUB 3 (לרוב הקוראים האלקטרוניים)", 34 | "format-epub-2": "EPUB 2 (לא בשימוש עוד, יכול לשמש קוראים אלקטרוניים ישנים מאוד)", 35 | "format-htmlz": "HTML", 36 | "format-mobi": "MOBI (למכשירי Kindle)", 37 | "format-pdf-a4": "PDF - גודל A4", 38 | "format-pdf-a5": "PDF - גודל A5", 39 | "format-pdf-a6": "PDF - גודל A6", 40 | "format-pdf-letter": "PDF - בגודל מכתב אמריקאי", 41 | "format-rtf": "RTF", 42 | "format-txt": "טקסט רגיל", 43 | "exceeded-rate-limitation": "הגשת יותר מדי בקשות בפרק זמן קצר. נא להמתין {{PLURAL:$1|דקה|$1 דקות}} לפני רענון הכלי הזה או להיכנס כדי למנוע מזה לקרות שוב.", 44 | "exception-invalid-format": "„$1” אינה תצורה תקינה. התצורות התקינות הן: $2", 45 | "font-field-label": "גופן", 46 | "no-font-option": "אין (להשתמש בבררת המחדל של המכשיר)", 47 | "font-field-help": "לבחור מבין $1 הגופנים הזמינים.", 48 | "options-label": "אפשרויות", 49 | "credits-field-label": "לוותר על הוקרה לעורכים (הורדה מהירה יותר)", 50 | "credits-default-message": "[רשימת העורכים הושמטה לבקשתך.]", 51 | "images-field-label": "לא לכלול תמונות", 52 | "nocache-field-label": "לעקוף את כל השמיר במטמון (אטי אבל יעיל יותר לניפוי שגיאות)", 53 | "export-button": "יצוא", 54 | "cache-updated": "המטמון התרענן לשפה: $1", 55 | "alert-close": "סגירה", 56 | "export-failed": "ההורדה נכשלה.", 57 | "exception-url-fetch-error": "לא ניתן למשוך את הכתובת: $1", 58 | "exception-rest-page-not-found": "לא ניתן למצוא את הספר ‚$1’.", 59 | "learn-more": "מידע נוסף.", 60 | "error-blocked-1": "כתובת ה־IP שלך חסומה כרגע.", 61 | "error-blocked-2": "כדי למנוע ניצול לרעה מצד אוטומציה מטרידנית, WS Export מונעת גישה ממשתמשים שלא נכנסו למערכת וחסומים גלובלית באתרי הוויקי של ויקימדיה.", 62 | "error-fix": "נא לנסות את הצעדים האלה אם נתקלת בשגיאה:", 63 | "error-fix-again": "נא לנסות להוריד את הספר שוב.", 64 | "error-fix-epub": "נא לנסות להוריד את הספר כקובץ EPUB.", 65 | "error-fix-nocredits": "ניתן לסמן את התיבה ‚אפשרויות’ כדי להסיר תודות", 66 | "error-fix-noimages": "ניתן לסמן את התיבה ‚אפשרויות’ כדי להסיר תמונות.", 67 | "error-breadcrumb": "שגיאה $1", 68 | "error-login": "נא להיכנס ל־WS Export כדי להמשיך.", 69 | "error-page-header": "אירעה שגיאה", 70 | "error-page-details": "פרטי השגיאה: $1", 71 | "error-page-issue": "אם השגיאה הזאת נמשכת, נא $1.", 72 | "error-page-issue-link": "לדווח על תקלה ב־Phabricator", 73 | "onwikiconfig-failure": "לא ניתן לקבל תצורה מהוויקי מתוך: $1", 74 | "exception-fetching-credits": "משיכת פרטי התודות נתקלה בשגיאה. נא לנסות לייצא שוב. אם השגיאה הזאת נמשכת, יש לסמן את התיבה ‚אפשרות’ כדי להסיר תודות.", 75 | "exception-book-conversion": "שגיאה בהמרת תצורת הספר.", 76 | "epub-title-page": "עמוד הכותרת", 77 | "epub-exported-date": "יוצא מוויקיטקסט ב־$1", 78 | "epub-about": "על אודות", 79 | "breadcrumbs-home-link": "דף הבית", 80 | "breadcrumbs-stats-link": "סטטיסטיקה", 81 | "recently-popular-heading": "נפוץ לאחרונה", 82 | "recently-popular-subtext": "ההורדות הנפוצות ביותר בשלושת החודשים האחרונים", 83 | "change-lang": "החלפת שפה" 84 | } 85 | -------------------------------------------------------------------------------- /i18n/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Hanna Tardos", 5 | "Luckylukee81", 6 | "Tacsipacsi" 7 | ] 8 | }, 9 | "app-subtitle": "Könyvek exportálása Wikiforrásokból többféle formátumba", 10 | "version": "$1 verzió", 11 | "licensed-under": "Elérhető a $1 licenc alatt.", 12 | "license-link": "GPL v2 vagy újabb", 13 | "source-and-docs": "A forráskód $1, dokumentáció pedig $2 található.", 14 | "source-link": "a GitHubon", 15 | "docs-link": "a többnyelvű Wikiforrásban", 16 | "statistics": "Van néhány $1 is.", 17 | "statistics-link": "statisztika", 18 | "issue-button": "Hibabejelentés", 19 | "back-to-wikisource": "Vissza a Wikiforráshoz", 20 | "export-book": "Könyv exportálása", 21 | "lang-field-label": "Wikiforrás", 22 | "lang-field-help": "Wikiforrás-nyelvkód vagy -aldomain, pl. „hu” vagy „en”.", 23 | "title-field-label": "Cím", 24 | "title-field-help": "A könyv címlapjának címe.", 25 | "format-field-label": "Fájlformátum", 26 | "format-epub-3": "EPUB 3 (a legtöbb e-olvasóhoz)", 27 | "format-epub-2": "EPUB 2 (elavult, néhány nagyon régi e-olvasóhoz hasznos lehet)", 28 | "format-htmlz": "HTML", 29 | "format-mobi": "MOBI (Kindle-höz)", 30 | "format-pdf-a4": "PDF – A4-es oldalméret", 31 | "format-pdf-a5": "PDF – A5-ös oldalméret", 32 | "format-pdf-a6": "PDF – A6-os oldalméret", 33 | "format-pdf-letter": "PDF - amerikai levélméret", 34 | "format-rtf": "RTF", 35 | "format-txt": "Egyszerű szöveg", 36 | "exception-invalid-format": "A(z) „$1” nem egy érvényes formátum. Az érvényes formátumok a következők: $2", 37 | "font-field-label": "Betűtípus", 38 | "no-font-option": "Nincs (az eszköz alapértelmezésének használata)", 39 | "font-field-help": "Válassz $1 elérhető betűtípus közül.", 40 | "options-label": "Opciók", 41 | "images-field-label": "Képek kihagyása", 42 | "nocache-field-label": "Minden gyorsítótárazás kihagyása (lassabb, de hibakereséshez hasznos)", 43 | "export-button": "Exportálás", 44 | "cache-updated": "A gyorsítótár frissítve a következő nyelvhez: $1", 45 | "alert-close": "Bezárás", 46 | "export-failed": "Sikertelen letöltés.", 47 | "learn-more": "Tudj meg többet.", 48 | "error-page-header": "Hiba történt", 49 | "epub-title-page": "Címlap", 50 | "epub-exported-date": "Exportálva a Wikiforrásból: $1", 51 | "epub-about": "Szerzőségi adatok" 52 | } 53 | -------------------------------------------------------------------------------- /i18n/is.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [] 4 | }, 5 | "epub-title-page": "Titill", 6 | "epub-exported-date": "Sótt frá Wikiheimild þann $1", 7 | "epub-about": "Um bókina" 8 | } 9 | -------------------------------------------------------------------------------- /i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "ACortellari", 5 | "Ajeje Brazorf", 6 | "Beta16", 7 | "McDutchie", 8 | "Toa" 9 | ] 10 | }, 11 | "footer-version": "Questa è la versione $2 di $1.", 12 | "version": "versione $1", 13 | "source-link": "su Github", 14 | "statistics-link": "statistiche", 15 | "back-to-wikisource": "Ritorna a Wikisource", 16 | "export-book": "Esporta un libro", 17 | "lang-field-label": "Wikisource", 18 | "lang-field-help": "Wikisource da cui esportare il libro.", 19 | "title-field-label": "Titolo", 20 | "format-field-label": "Formato file", 21 | "format-htmlz": "HTML", 22 | "format-pdf-a5": "PDF - Formato A5", 23 | "format-pdf-a6": "PDF - Formato A6", 24 | "format-rtf": "RTF", 25 | "format-txt": "Testo normale", 26 | "font-field-label": "Carattere", 27 | "font-field-help": "Scegli tra $1 tipi di carattere disponibili.", 28 | "options-label": "Opzioni", 29 | "export-button": "Esporta", 30 | "alert-close": "Chiudi", 31 | "learn-more": "Ulteriori informazioni.", 32 | "error-breadcrumb": "Errore $1", 33 | "epub-title-page": "Titolo", 34 | "epub-exported-date": "Esportato da Wikisource il $1. Segnala eventuali errori su it.wikisource.org/wiki/Segnala_errori", 35 | "epub-about": "Informazioni", 36 | "breadcrumbs-stats-link": "Statistiche" 37 | } 38 | -------------------------------------------------------------------------------- /i18n/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "A2y4", 5 | "Likibp", 6 | "MathXplore", 7 | "Omotecho", 8 | "Shirayuki", 9 | "Tamaki Wakita" 10 | ] 11 | }, 12 | "app-title": "ウィキソースの書籍の書き出し", 13 | "app-title-wikisource": "ウィキソース", 14 | "app-title-html": "$1の書籍の書き出し", 15 | "app-subtitle": "さまざまなファイル形式でウィキソースから書籍を書き出します。", 16 | "logo-alt-text": "ウィキソースのロゴ、青と白で描かれた氷山のイラスト。", 17 | "footer-version": "これは$1バージョン$2です。", 18 | "version": "バージョン$1", 19 | "commit-label": "コミット:$1", 20 | "licensed-under": "$1でライセンスされています。", 21 | "license-link": "GPL v2以降", 22 | "source-and-docs": "ソースコードは$1、いくつかのドキュメントは$2入手できます。", 23 | "source-link": "Githubから", 24 | "docs-link": "多言語ウィキソースから", 25 | "statistics": "いくつかの$1も提供しています。", 26 | "statistics-link": "統計", 27 | "issues": "Phabricatorの$1プロジェクトに、issuesを報告してください。", 28 | "issue-button": "issueを作成", 29 | "back-to-wikisource": "ウィキソースへ戻る", 30 | "previous-alt-text": "リンクがウィキソースに戻ることを示す、後ろ向きの矢印アイコン。", 31 | "export-book": "書籍を書き出す", 32 | "lang-field-label": "ウィキソース", 33 | "lang-field-help": "書籍の書き出し元となるウィキソース。", 34 | "title-field-label": "題名", 35 | "title-field-help": "書き出す書籍またはセクションの題名。", 36 | "format-field-label": "ファイル形式", 37 | "format-epub-3": "EPUB 3(主要な電子書籍リーダー用)", 38 | "format-epub-2": "EPUB 2(非推奨、一部の非常に古い電子リーダーには役立つ場合があります)", 39 | "format-htmlz": "HTML", 40 | "format-mobi": "MOBI(Kindle用)", 41 | "format-pdf-a4": "PDF - A4サイズ", 42 | "format-pdf-a5": "PDF - A5サイズ", 43 | "format-pdf-a6": "PDF - A6サイズ", 44 | "format-pdf-letter": "PDF - USレターサイズ", 45 | "format-rtf": "RTF", 46 | "format-txt": "プレーンテキスト", 47 | "exceeded-rate-limitation": "短時間の間にあまりにも多くのリクエストを行っています。このツールを再読み込みする前に $1 {{PLURAL:$1|分間}}、待機するか、この問題の再発を防ぐにはログインしてください。", 48 | "exception-invalid-format": "「$1」は有効なファイル形式ではありません。有効なファイル形式は次の通りです:$2", 49 | "font-field-label": "フォント", 50 | "no-font-option": "なし(デバイスの規定のフォントを使用する)", 51 | "font-field-help": "$1種類のフォントから選択できます。", 52 | "options-label": "オプション:", 53 | "credits-field-label": "編集者のクレジットを除外する(ダウンロードの高速化)", 54 | "credits-default-message": "[ご要望にお応えして、寄稿者一覧は省略しました。]", 55 | "images-field-label": "画像を含めない", 56 | "nocache-field-label": "すべてのキャッシュを無効にする(遅くなりますが、デバッグ時に便利です)", 57 | "export-button": "書き出し", 58 | "cache-updated": "次の言語向けにキャッシュが更新されました:$1", 59 | "alert-close": "閉じる", 60 | "export-failed": "ダウンロードに失敗しました。", 61 | "exception-url-fetch-error": "次のURLを取得できませんでした:$1", 62 | "exception-rest-page-not-found": "書籍「$1」は見つかりませんでした。", 63 | "learn-more": "もっと詳しく。", 64 | "error-blocked-1": "あなたのIPアドレスは現在ブロックされています。", 65 | "error-blocked-2": "破壊的な自動化による悪用防止のため、WS Exportは、ウィキメディアのウィキ群においてグローバルブロックされているログアウトした利用者からのアクセスを拒否します。", 66 | "error-fix": "エラーが発生した場合は次の手順をお試しください:", 67 | "error-fix-again": "もう一度書籍をダウンロードしてみて下さい。", 68 | "error-fix-epub": "EPUBファイルの書籍でダウンロードしてみて下さい。", 69 | "error-fix-nocredits": "クレジットを削除するには「オプション」のチェックボックスをオンにします", 70 | "error-fix-noimages": "画像を削除するには、「オプション」のチェックボックスをオンにします。", 71 | "error-breadcrumb": "エラー$1", 72 | "error-login": "続行するには、WS Exportにログインしてください。", 73 | "error-page-header": "エラーが発生しました", 74 | "error-page-details": "エラーについての詳細:$1", 75 | "error-page-issue": "このエラーが解決しない場合は、$1をご覧ください。", 76 | "error-page-issue-link": "Phabricatorに関する問題を報告する", 77 | "onwikiconfig-failure": "次のウィキ上の設定を取得できませんでした: $1", 78 | "exception-fetching-credits": "クレジット情報の取得時にエラーが発生しました。もう一度エクスポートを試してください。このエラーが解決しない場合は、「オプション」でクレジットを削除するにチェックを入れてください。", 79 | "exception-book-conversion": "書籍のフォーマットの変換エラー。", 80 | "epub-title-page": "表題紙", 81 | "epub-exported-date": "$1にウィキソースから書き出されました。", 82 | "epub-about": "About", 83 | "breadcrumbs-stats-link": "統計" 84 | } 85 | -------------------------------------------------------------------------------- /i18n/ka.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Mehman97" 5 | ] 6 | }, 7 | "app-title": "ვიკიწყაროს წიგნის ექსპორტი", 8 | "app-title-html": "$1 წიგნის ექსპორტი", 9 | "app-subtitle": "ვიკიწყაროდან წიგნების ექსპორტი სხვადასხვა ფაილის ფორმატში.", 10 | "logo-alt-text": "ვიკიწყაროს ლოგო, აისბერგის ლურჯ-თეთრი ილუსტრაცია.", 11 | "footer-version": "ეს არის $1 ვერსია $2.", 12 | "version": "ვერსია $1", 13 | "commit-label": "შესრულება: $1", 14 | "licensed-under": "ლიცენზირებულია $1.", 15 | "license-link": "GPL v2 ან უფრო ძველი", 16 | "alert-close": "დახურვა", 17 | "export-failed": "ჩამოტვირთვა ვერ მოხერხდა.", 18 | "exception-url-fetch-error": "URL-ის მოძიება შეუძლებელია: $1", 19 | "exception-rest-page-not-found": "გვერდი '$1' ვერ მოიძებნა.", 20 | "learn-more": "გაიგეთ მეტი.", 21 | "error-blocked-1": "თქვენი IP მისამართი ამჟამად დაბლოკილია.", 22 | "error-page-issue-link": "Phabricator-ზე პრობლემის შეტყობინება", 23 | "epub-title-page": "სათაურის გვერდი", 24 | "epub-exported-date": "ექსპორტირებულია ვიკიწყაროდან $1-ზე", 25 | "epub-about": "შესახებ", 26 | "breadcrumbs-home-link": "მთავარი", 27 | "breadcrumbs-stats-link": "სტატისტიკა", 28 | "recently-popular-heading": "ბოლო დროს პოპულარული", 29 | "recently-popular-subtext": "ბოლო სამი თვის ყველაზე პოპულარული ჩამოტვირთვები", 30 | "change-lang": "ენის შეცვლა" 31 | } 32 | -------------------------------------------------------------------------------- /i18n/kg.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Cgmbo" 5 | ] 6 | }, 7 | "lang-field-label": "Wikisource", 8 | "lang-field-help": "Wikisource kisika ya kubakila mukanda.", 9 | "title-field-label": "Ntu-diambu", 10 | "title-field-help": "Ntu-diambu ya mukanda to kitini ya kubaka.", 11 | "format-field-label": "Format ya fichier", 12 | "format-epub-3": "EPUB 3 (sambu na bantu mingi ya ke tanga en ligne)", 13 | "format-htmlz": "HTML", 14 | "format-pdf-a4": "PDF - Nene A4", 15 | "format-pdf-a5": "PDF - Nene A5", 16 | "format-pdf-a6": "PDF - Nene A6", 17 | "format-pdf-letter": "PDF - Nene ya mukanda ya États-Unis", 18 | "format-rtf": "RTF", 19 | "exception-invalid-format": "\"$1\" kele ve format ya mbote. Baformat ya mbote kele: $2", 20 | "font-field-label": "Fonte", 21 | "no-font-option": "Mosi ve (sadila ntalu ya défaut ya apareyi)", 22 | "font-field-help": "Pona banda na $1 ya bafonte yina kele.", 23 | "options-label": "Bansola", 24 | "credits-default-message": "[Beto me katula liste ya bantu ya ke sadisaka mutindu bo lombaka.]", 25 | "images-field-label": "Kuyika ve bifwanisu", 26 | "alert-close": "Kanga", 27 | "export-failed": "Mpila ya kubaka kele ve.", 28 | "exception-url-fetch-error": "Mpila kele ya kukota na URL: $1", 29 | "exception-rest-page-not-found": "Beto me mona ve mukanda '$1'.", 30 | "learn-more": "Zaba mambu mingi.", 31 | "error-fix": "Sadila bitambi yai kana nge me zwa kifu:", 32 | "error-fix-again": "Meka kubaka diaka mukanda.", 33 | "error-fix-epub": "Meka kubaka mukanda na format EPUB.", 34 | "error-fix-nocredits": "Pona lupangu 'Bansola' sambu na kukatuka ba crédit", 35 | "error-fix-noimages": "Pona lupangu 'Bansola' sambu na kukatuka bifwanisu.", 36 | "error-breadcrumb": "Kifu $1", 37 | "error-page-header": "Kifu mosi me salama", 38 | "error-page-details": "Bansangu ya me tala kifu: $1", 39 | "error-page-issue": "Kana kifu yai ke katuka ve, pardo $1.", 40 | "error-page-issue-link": "sala rapore ya diambu yai na Phabricator", 41 | "exception-book-conversion": "Kifu ke salama na ntangu ya kusoba format ya mukanda.", 42 | "epub-title-page": "Lutiti ya ntu-diambu", 43 | "epub-exported-date": "Beto me baka yo katuka na Wikisource na $1", 44 | "epub-about": "Ya me tala" 45 | } 46 | -------------------------------------------------------------------------------- /i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Gustmd7410", 5 | "Suleiman the Magnificent Television", 6 | "YeBoy371", 7 | "Ykhwong", 8 | "그냥기여자" 9 | ] 10 | }, 11 | "app-title": "위키문헌 책 내보내기", 12 | "app-title-wikisource": "위키문헌", 13 | "app-title-html": "$1 책 내보내기", 14 | "app-subtitle": "위키문헌의 문헌들을 다양한 형식의 파일로 내보내십시오.", 15 | "logo-alt-text": "위키문헌 로고. 파란색, 하얀색으로 구성된 빙산 일러스트.", 16 | "footer-version": "$1의 $2 버전입니다.", 17 | "version": "버전 $1", 18 | "commit-label": "커밋: $1", 19 | "licensed-under": "$1 라이선스로 배포됩니다.", 20 | "license-link": "GPL v2 이상", 21 | "source-and-docs": "$1 소스 코드를, $2 관련 문서를 확인할 수 있습니다.", 22 | "source-link": "깃허브에서", 23 | "docs-link": "다언어 위키문헌에서", 24 | "statistics": "일부 $1도 제공합니다.", 25 | "statistics-link": "통계", 26 | "issues": "파브리케이터의 $1 프로젝트에서 이슈를 보고해 주십시오.", 27 | "issue-button": "이슈 만들기", 28 | "back-to-wikisource": "위키문헌으로 돌아가기", 29 | "previous-alt-text": "위키소스에 링크가 돌아간다는 것을 나타내는 뒤로 향하는 화살표 아이콘", 30 | "export-book": "책 내보내기", 31 | "lang-field-label": "위키문헌", 32 | "lang-field-help": "책을 내보낼 위키문헌입니다.", 33 | "title-field-label": "제목", 34 | "title-field-help": "내보낼 책 또는 문단의 제목입니다.", 35 | "format-field-label": "파일 포맷", 36 | "format-epub-3": "EPUB 3 (대부분의 전자책 단말기용)", 37 | "format-epub-2": "EPUB 2 (구식이지만 매우 오래된 일부 전자책 단말기에 유용할 수 있음)", 38 | "format-htmlz": "HTML", 39 | "format-mobi": "MOBI (킨들용)", 40 | "format-pdf-a4": "PDF - A4 크기", 41 | "format-pdf-a5": "PDF - A5 크기", 42 | "format-pdf-a6": "PDF - A6 크기", 43 | "format-pdf-letter": "PDF - US 레터 크기", 44 | "format-rtf": "RTF", 45 | "format-txt": "일반 텍스트", 46 | "exceeded-rate-limitation": "짧은 시간 동안 너무 많은 요청을 수행하고 있습니다. 이 도구를 다시 로드하기 전에 $1{{PLURAL:$1|분}}을 기다리거나, 이 문제가 다시 발생하지 않도록 로그인하세요.", 47 | "exception-invalid-format": "\"$1\"은(는) 유효한 포맷이 아닙니다. 유효한 포맷은 다음과 같습니다: $2", 48 | "font-field-label": "글꼴", 49 | "no-font-option": "없음 (장치 기본값 사용)", 50 | "font-field-help": "사용 가능한 $1개 글꼴에서 선택합니다.", 51 | "options-label": "옵션", 52 | "credits-field-label": "편집자 정보 제외 (더 빠른 다운로드)", 53 | "credits-default-message": "[기여자 목록은 요청에 따라 제외되었습니다.]", 54 | "images-field-label": "이미지를 포함하지 않음", 55 | "nocache-field-label": "모든 캐시 우회 (속도는 더 느리지만 디버깅에 유용함)", 56 | "export-button": "내보내기", 57 | "cache-updated": "다음 언어에 대한 캐시를 새로 고쳤습니다: $1", 58 | "alert-close": "닫기", 59 | "export-failed": "다운로드를 실패했습니다.", 60 | "exception-url-fetch-error": "URL을 검색할 수 없습니다: $1", 61 | "exception-rest-page-not-found": "'$1' 페이지를 찾을 수 없습니다.", 62 | "learn-more": "더 알아봅니다.", 63 | "error-blocked-1": "당신의 IP 주소는 현재 차단되었습니다.", 64 | "error-blocked-2": "파괴적인 자동화의 악용을 방지하기 위해 WS Export는 위키미디어 위키에서 전역 차단된 로그아웃 사용자에 대한 액세스를 거부합니다.", 65 | "error-fix": "오류가 발생하는 경우 다음 단계를 시도해 주십시오:", 66 | "error-fix-again": "책 다운로드를 다시 시도합니다.", 67 | "error-fix-epub": "EPUB 파일로 책 다운로드를 시도합니다.", 68 | "error-fix-nocredits": "크레딧을 제거하려면 '옵션' 내 상자에 체크하십시오.", 69 | "error-fix-noimages": "이미지를 제거하려면 '옵션' 내 상자에 체크하십시오.", 70 | "error-breadcrumb": "오류 $1", 71 | "error-login": "계속하려면 위키문헌 내보내기에 로그인하십시오.", 72 | "error-page-header": "오류가 발생했습니다", 73 | "error-page-details": "오류 상세 정보: $1", 74 | "error-page-issue": "이 오류가 지속되면 $1해 주십시오.", 75 | "error-page-issue-link": "파브리케이터에 이슈를 보고", 76 | "onwikiconfig-failure": "$1에서 위키 설정을 가져올 수 없습니다.", 77 | "exception-fetching-credits": "크레딧 정보를 가져올 때 오류가 발생했습니다. 내보내기를 다시 시도해 보세요. 이 오류가 지속되면 '옵션'에서 크레딧을 제거하는 상자에 체크하세요.", 78 | "exception-book-conversion": "책의 포맷을 변환하는 중 오류가 발생했습니다.", 79 | "epub-title-page": "표지", 80 | "epub-exported-date": "$1 위키문헌에서 내보냄", 81 | "epub-about": "정보", 82 | "breadcrumbs-home-link": "홈", 83 | "breadcrumbs-stats-link": "통계", 84 | "recently-popular-heading": "최근 인기", 85 | "recently-popular-subtext": "지난 3개월 동안 가장 인기 있는 다운로드", 86 | "change-lang": "언어 변경" 87 | } 88 | -------------------------------------------------------------------------------- /i18n/ku-latn.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Bikarhêner" 5 | ] 6 | }, 7 | "app-title": "Wîkîçavkanî Eksportkirina Kitêban", 8 | "app-title-html": "$1 Eksportkirina Kitêban", 9 | "app-subtitle": "Kitêban ji Wîkîçavkaniyê bi çendîn formatên dosyeyan derxîne.", 10 | "logo-alt-text": "Logoya Wîkîçavkaniyê, temsîla çiyayeke qeşayê yê şîn û spî", 11 | "footer-version": "Ev versiyona $2 yê ji bo $1 ye.", 12 | "version": "versiyon $1", 13 | "commit-label": "Versiyona qeydkirî: $1", 14 | "licensed-under": "Ew li bin $1 lîsanskirî ye.", 15 | "license-link": "GPL v2 an nûtir", 16 | "source-and-docs": "Koda çavkaniyê li $1 û hin dokûmentasyon li $2 berdest e.", 17 | "source-link": "li ser Githubê", 18 | "docs-link": "li ser Wîkîçavkaniya pirzimanî", 19 | "statistics": "Hin $1 herwisa hatine peydakirin.", 20 | "statistics-link": "statîstîk", 21 | "issues": "Ji kerema xwe pirsgirêkan li ser Fabrîkatorê di bin projeya $1 de ragihîne.", 22 | "issue-button": "Pirsgirêkê çêke", 23 | "back-to-wikisource": "Vegere Wîkîçavkaniyê", 24 | "export-book": "Kitêbekî derxîne", 25 | "lang-field-label": "Wîkîçavkanî", 26 | "lang-field-help": "Wîkîçavkanî wê ji kîjanê kitêbê eksport bike.", 27 | "title-field-label": "Sernav", 28 | "title-field-help": "Sernavê kitêbê an beşa ku were eksportkirin.", 29 | "format-field-label": "Formata dosyeyê", 30 | "format-field-help": "", 31 | "format-epub-3": "EPUB 3 (ji bo piraniya e-xwîneran)", 32 | "format-epub-2": "EPUB 2 (kevinbûyî, dibe ku ji bo hin e-xwînerên pir kevin re bi kêr were)", 33 | "format-htmlz": "HTML", 34 | "format-mobi": "MOBI (ji bo Kindle-yan)", 35 | "format-pdf-a4": "PDF - bi mezinahiya A4-ê", 36 | "format-pdf-a5": "PDF - bi mezinahiya A5-ê", 37 | "format-pdf-a6": "PDF - bi mezinahiya A6-ê", 38 | "format-pdf-letter": "PDF - bi mezinahiya US letter-ê", 39 | "format-rtf": "RTF", 40 | "format-txt": "Nivîsa sade", 41 | "exception-invalid-format": "\"$1\" ne formateke derbasdar e. Formatên derbasdar ev in: $2", 42 | "font-field-label": "Font", 43 | "no-font-option": "Tine (standarda cîhazê bi kar bîne)", 44 | "font-field-help": "Ji $1 fonts berdest bibijêre.", 45 | "options-label": "Vebijêrk", 46 | "credits-field-label": "Beşdariyên edîtoran xaric bigire (daxistina leztir)", 47 | "credits-default-message": "[Wekî ku hatibû xwestin, lîsteya beşdaran hate derbaskirin.]", 48 | "images-field-label": "Wêne tevlî neke", 49 | "nocache-field-label": "Temamiya pêştomarakê derbas bike (hêditir e lê kêrhatîtir e ji bo sererastkirina xetayan)", 50 | "export-button": "Derxîne", 51 | "cache-updated": "Pêştomark hatin nûkirin ji bo zimanê: $1" 52 | } 53 | -------------------------------------------------------------------------------- /i18n/lb.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Robby", 5 | "Volvox" 6 | ] 7 | }, 8 | "logo-alt-text": "Wikisource-Logo, eng blo-wäiss Illustratioun vun engem Äisbierg.", 9 | "footer-version": "Dëst ass d'Versioun $2 vu(n) $1.", 10 | "version": "Versioun $1", 11 | "licensed-under": "Et ass lizenzéiert ënner $1.", 12 | "source-link": "op Github", 13 | "statistics-link": "Statistiken", 14 | "back-to-wikisource": "Zréck op Wikisource", 15 | "export-book": "E Buch exportéieren", 16 | "lang-field-label": "Wikisource", 17 | "title-field-label": "Titel", 18 | "format-field-label": "Format vum Fichier", 19 | "format-htmlz": "HTML", 20 | "format-pdf-a4": "PDF - Gréisst A4", 21 | "format-pdf-a5": "PDF - Gréisst A5", 22 | "format-pdf-a6": "PDF - Gréisst A6", 23 | "format-pdf-letter": "PDF - Gréisst US letter", 24 | "format-rtf": "RTF", 25 | "exception-invalid-format": "\"$1\" ass kee gültegt Format. Gülteg Formater sinn: $2", 26 | "font-field-label": "Schrëftaart", 27 | "font-field-help": "Wielt aus $1 verfügbare Schrëftaarten.", 28 | "options-label": "Optiounen", 29 | "export-button": "Exportéieren", 30 | "alert-close": "Zoumaachen", 31 | "exception-rest-page-not-found": "D'Säit „$1“ gouf net fonnt.", 32 | "learn-more": "Fir méi ze wëssen", 33 | "error-blocked-1": "Är IP-Adress ass am Moment gespaart.", 34 | "error-breadcrumb": "Feeler $1", 35 | "error-page-header": "Et ass e Feeler geschitt", 36 | "error-page-details": "Feelerdetailer: $1", 37 | "error-page-issue": "Wann dëse Feeler bestoe bleift, wgl. $1.", 38 | "epub-about": "Iwwer", 39 | "breadcrumbs-home-link": "Haaptsäit", 40 | "breadcrumbs-stats-link": "Statistiken", 41 | "change-lang": "Sprooch wiesselen" 42 | } 43 | -------------------------------------------------------------------------------- /i18n/lt.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Nokeoo" 5 | ] 6 | }, 7 | "app-title": "Vikišaltinių knygų eksportavimas", 8 | "app-title-html": "$1 knygos eksportavimas", 9 | "app-subtitle": "Eksportuokite knygas iš Vikišaltinių įvairiais failų formatais.", 10 | "logo-alt-text": "Vikišaltinių logotipas, mėlyna ir balta ledkalnio iliustracija.", 11 | "footer-version": "Tai $1 $2 versija.", 12 | "version": "$1 versija", 13 | "licensed-under": "Taikoma $1 licencija.", 14 | "license-link": "GPL v2 arba naujesnis", 15 | "source-and-docs": "Šaltinio kodas prieinamas $1, o kai kurie dokumentai – $2.", 16 | "source-link": "„Github“", 17 | "docs-link": "Daugiakalbiuose Vikišaltiniuose", 18 | "statistics": "Taip pat pateikiama $1.", 19 | "statistics-link": "statistika", 20 | "issues": "Praneškite apie problemas, susijusias su $1 projektu, Fabrikatoriuje.", 21 | "issue-button": "Sukurti problemą", 22 | "back-to-wikisource": "Grįžti į Vikišaltinius", 23 | "previous-alt-text": "Atgal nukreiptos rodyklės piktograma, nurodanti, kad nuoroda grįžta į Vikišaltinius.", 24 | "export-book": "Eksportuoti knygą", 25 | "lang-field-label": "Vikišaltiniai", 26 | "lang-field-help": "Vikišaltinis, iš kurio eksportuoti knygą.", 27 | "title-field-label": "Pavadinimas", 28 | "title-field-help": "Eksportuotinos knygos ar skyriaus pavadinimas.", 29 | "format-field-label": "Failo formatas", 30 | "format-epub-3": "EPUB 3 (daugeliui skaityklių)", 31 | "format-epub-2": "EPUB 2 (nebenaudojama, gali būti naudinga kai kurioms labai senoms skaityklėms)", 32 | "format-htmlz": "HTML", 33 | "format-mobi": "MOBI (skirta Kindle)", 34 | "format-pdf-a4": "PDF - A4 dydžio", 35 | "format-pdf-a5": "PDF - A5 dydžio", 36 | "format-pdf-a6": "PDF - A6 dydžio", 37 | "format-pdf-letter": "PDF - JAV laiško dydžio", 38 | "format-rtf": "RTF", 39 | "format-txt": "Paprastas tekstas", 40 | "exceeded-rate-limitation": "Per trumpą laiką pateikiate per daug užklausų. Palaukite $1 {{PLURAL:$1|minutę|minutes}} prieš iš naujo įkeldami šį įrankį arba prisijunkite, kad tai nepasikartotų.", 41 | "exception-invalid-format": "„$1“ nėra galimas formatas. Galimi formatai: $2", 42 | "font-field-label": "Šriftas", 43 | "no-font-option": "Nėra (naudoti numatytą įrenginyje)", 44 | "font-field-help": "Pasirinkite iš $1 galimų šriftų.", 45 | "options-label": "Parinktys", 46 | "credits-field-label": "Neįtraukti redaktorių kreditų (greitesnis atsisiuntimas)", 47 | "credits-default-message": "[Asmenų su indėliu sąrašas išimtas pagal prašymą.]", 48 | "images-field-label": "Neįtraukti paveikslėlių", 49 | "export-button": "Eksportuoti", 50 | "alert-close": "Uždaryti", 51 | "export-failed": "Atsisiuntimas nepavyko.", 52 | "exception-url-fetch-error": "Nepavyko gauti URL: $1", 53 | "exception-rest-page-not-found": "Nepavyko rasti knygos „$1“.", 54 | "learn-more": "Sužinokite daugiau.", 55 | "error-blocked-1": "Jūsų IP adresas šiuo metu užblokuotas.", 56 | "error-blocked-2": "Siekiant išvengti piktnaudžiavimo dėl trikdančio automatizavimo, VS eksportavimas draudžia prieigą atsijungusiems naudotojams, kurie visuotinai blokuojami Vikimedijos viki.", 57 | "error-fix": "Jei susiduriate su klaida, pabandykite šiuos veiksmus:", 58 | "error-fix-again": "Pabandykite vėl atsisiųsti knygą.", 59 | "error-fix-epub": "Pabandykite atsisiųsti knygą kaip EPUB failą.", 60 | "error-fix-nocredits": "Pažymėkite langelį skiltyje „Parinktys“, kad pašalintumėte kreditus", 61 | "error-fix-noimages": "Pažymėkite langelį skiltyje „Parinktys“, kad pašalintumėte paveikslėlius.", 62 | "error-breadcrumb": "Klaida $1", 63 | "error-login": "Jei norite tęsti, prisijunkite prie VS eksportavimo.", 64 | "error-page-header": "Įvyko klaida", 65 | "error-page-details": "Klaidos informacija: $1", 66 | "error-page-issue": "Jei ši klaida išlieka, prašome $1.", 67 | "error-page-issue-link": "pranešti apie problemą Fabrikatoriuje.", 68 | "onwikiconfig-failure": "Nepavyko gauti viki konfigūracijos iš: $1", 69 | "exception-fetching-credits": "Klaida gaunant kreditų informaciją. Bandykite eksportuoti dar kartą. Jei ši klaida kartojasi, pažymėkite laukelį „Parinktyse“, kad pašalintumėte kreditus.", 70 | "exception-book-conversion": "Klaida konvertuojant knygos formatą.", 71 | "epub-title-page": "Titulinis puslapis", 72 | "epub-exported-date": "Eksportuota iš Vikišaltinių $1", 73 | "epub-about": "Apie" 74 | } 75 | -------------------------------------------------------------------------------- /i18n/no.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [] 4 | }, 5 | "epub-title-page": "Forside", 6 | "epub-exported-date": "Eksportert fra Wikikilden den $1", 7 | "epub-about": "Om" 8 | } 9 | -------------------------------------------------------------------------------- /i18n/om.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Ahrada2016", 5 | "Lalise2021" 6 | ] 7 | }, 8 | "app-title": "Alergi Kitaaba Wikisource", 9 | "app-title-html": "Alergi Kitaaba $1", 10 | "app-subtitle": "Wikisources keessaa kitaabota alergi harshama dhangiilee gosa baay'een.", 11 | "logo-alt-text": "Logoon maddawiki, cuquliisaa fi adiin kan qarsaa cabbii agarsiisa.", 12 | "footer-version": "$1 kun hiika $2 dha.", 13 | "version": "Hiika $1", 14 | "commit-label": "Murannoo:$1", 15 | "licensed-under": "$1 jalatti eeyyamaayeera.", 16 | "license-link": "Qubee GPL v2", 17 | "source-and-docs": "Maddi kooddii mul'ifamaa $1 fi Dokmenteessuu hagitokko $2.", 18 | "source-link": "Github irratti", 19 | "docs-link": "Afdanee Wikisource irratti", 20 | "statistics": "$1 hagitokkos dhiyaateera.", 21 | "statistics-link": "istaatiksii", 22 | "issues": "Maaloo Pabrikeeter irratti piroojektii $1 jalatti yaadrimee gabaasi.", 23 | "issue-button": "Giroo uumi", 24 | "back-to-wikisource": "Gara Wikisource deebi'i", 25 | "previous-alt-text": "Sajoon biyyaa Duuba-deebisuu agarsiisu hidhaa Maddawikitti ittiin deebinuu dha.", 26 | "export-book": "Kitaaba alergi", 27 | "lang-field-label": "Maddawiki", 28 | "lang-field-help": "Kitaaba alerguuf Wikisource irrayi.", 29 | "title-field-label": "Matduree", 30 | "title-field-help": "Matduree ykn kutaa kitaabaa alerguuf.", 31 | "format-field-label": "Bifunka harshamaa", 32 | "format-field-help": "GPL v2", 33 | "format-epub-3": "EPUB 3", 34 | "format-epub-2": "EPUB 2", 35 | "format-htmlz": "HTML", 36 | "format-mobi": "MOBI(qoosaaf)", 37 | "format-pdf-a4": "PDF - hammamtaa A4", 38 | "format-pdf-a5": "PDF-bal'na A5", 39 | "format-pdf-a6": "PDF - hammamtaa A6", 40 | "format-pdf-letter": "PDF - Hammamtaa qubee US", 41 | "format-rtf": "RTF", 42 | "format-txt": "Barquu", 43 | "exception-invalid-format": "\"$1\" bifunka fayyadu miti. Bifunki fayyadu: $2 dha.", 44 | "font-field-label": "Bocquu", 45 | "no-font-option": "Hinjiru (divaayisii fafmalee fayyadami)", 46 | "font-field-help": "$1 keessaa hamgosa mul'ataa filadhu.", 47 | "options-label": "Filannoolee", 48 | "credits-field-label": "Ceegoo gulaalaan ala (gaffe'aa ariifataa)", 49 | "credits-default-message": "Tarreeffamni hirmaattotaa kanneen haqaman akka eeggataatti of keessatti ni qabata.", 50 | "images-field-label": "Suuraa hin dabalatu", 51 | "nocache-field-label": "Kuftaalee hunda irradarbuu (suutumaa dha garuu xaliilessuuf bayyee faayyada)", 52 | "export-button": "Alergi", 53 | "cache-updated": "Kuftaleen haaromfameera afaan: $1", 54 | "alert-close": "Cufi", 55 | "export-failed": "Gafe'uun kufeera.", 56 | "exception-url-fetch-error": "URL kebaafachuu hindanda'u:$1", 57 | "exception-rest-page-not-found": "Kitaabni '$1' as keessa hin jiru.", 58 | "learn-more": "Dabalataan baradhu.", 59 | "error-fix": "Maaloo gulantaalee kana yaali yoo dogoggorri si qunname.", 60 | "error-fix-again": "Kitaabicha buusuuf irradeebiin yaali.", 61 | "error-fix-epub": "Harshama EPUBtiin Kitaaba gaffe'uu yaali.", 62 | "error-fix-nocredits": "'Filannoolee' keessa dulaaba soqi suuraalee haquuf", 63 | "error-fix-noimages": "'Filannoolee' keessaa dulaaba ilaali suuraawwan balleessuuf.", 64 | "error-breadcrumb": "$1 dogoggoraa", 65 | "error-page-header": "Mudattee dogoggoraa", 66 | "error-page-details": "Dogoggora gadi fageenyaan:$1", 67 | "error-page-issue": "Dogoggora kan yoo mormite, maloo $1.", 68 | "error-page-issue-link": "Warshaa irratti rakkoo gabaasi", 69 | "onwikiconfig-failure": "Tessumooma wiki-irratti kebaafachuu hin danda'a:$1", 70 | "exception-fetching-credits": "Odeeffannoo ceegoo hawwachuu dogoggoraa dha. Maloo alerguu irradeebiin yaali. Dogoggora kana yoo mormite, ceegoowwan haquuf 'filannoolee' keessa dulaaba soqi.", 71 | "exception-book-conversion": "Diddiirraa dogoggoraa unka kitaabaa.", 72 | "epub-title-page": "Fuula matduree", 73 | "epub-exported-date": "Wikisource irraa gara $1tti alergameera", 74 | "epub-about": "Waa'ee" 75 | } 76 | -------------------------------------------------------------------------------- /i18n/or.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Psubhashish" 5 | ] 6 | }, 7 | "app-title": "ଉଇକିପାଠାଗାର ବହି ଏକ୍ସପୋର୍ଟ", 8 | "app-title-html": "$1 ବହି ଏକ୍ସପୋର୍ଟ", 9 | "app-subtitle": "ଉଇକିପାଠାଗାରରୁ ବହିସବୁ ବିଭିନ୍ନ ଫର୍ମାଟରେ ଏକ୍ସପୋର୍ଟ କରନ୍ତୁ ।", 10 | "logo-alt-text": "ଉଇକିପାଠାଗାର ଲୋଗୋ - ଏକ ବରଫଖଣ୍ଡର ନୀଳ ଓ ଧଳା ଅଙ୍କନ ।", 11 | "footer-version": "ଏହା $2ର $1 ତମ ସଂସ୍କରଣ ।", 12 | "version": "$1 ତମ ସଂସ୍କରଣ", 13 | "commit-label": "କମିଟ: $1", 14 | "licensed-under": "ଏଥିରେ $1 ଲାଇସେନ୍ସ ଲାଗିଛି ।", 15 | "license-link": "GPL v2 କିମ୍ବା ପରବର୍ତ୍ତୀ", 16 | "source-and-docs": "ସୋର୍ସ କୋଡ଼ $1ରେ ଏବଂ କିଛି ସବିଶେଷ ଲିଖନ $2ରେ ମିଳିପାରିବ ।", 17 | "source-link": "Githubରେ", 18 | "docs-link": "ବହୁଭାଷୀ ଉଇକିପାଠାଗାରରେ", 19 | "statistics": "କେତେକ $1 ମଧ୍ୟ ଦିଆଯାଇଛି ।", 20 | "statistics-link": "ପରିସଂଖ୍ୟାନ", 21 | "issues": "ଦୟାକରି ଫାବ୍ରିକେଟରର $1 ପ୍ରକଳ୍ପରେ ଅସୁବିଧାସବୁ ଜଣାନ୍ତୁ ।", 22 | "issue-button": "ଅସୁବିଧାଟିଏ ଜଣାନ୍ତୁ", 23 | "back-to-wikisource": "ଉଇକିପାଠାଗାରକୁ ଫେରନ୍ତୁ", 24 | "previous-alt-text": "ଲିଙ୍କଟି ଉଇକିପାଠାଗାରକୁ ଫେରାଏ ବୋଲି ପଛମୁହାଁ ତୀର ଚିହ୍ନ ଆଇକନ ଦର୍ଶାଏ ।", 25 | "export-book": "ବହିଟିଏ ଏକ୍ସପୋର୍ଟ କରନ୍ତୁ", 26 | "lang-field-label": "ଉଇକିପାଠାଗାର", 27 | "lang-field-help": "ଯେଉଁ ଉଇକିପାଠାଗାରରୁ ବହିଟି ଏକ୍ସପୋର୍ଟ କରିବେ ।", 28 | "title-field-label": "ନାମ", 29 | "title-field-help": "ବହିର ନାମ ବା ଏକ୍ସପୋର୍ଟ କରାଯିବା ଅଂଶ ।", 30 | "format-field-label": "ଫାଇଲ ପ୍ରକାର", 31 | "format-epub-3": "EPUB 3 (ଅଧିକାଂଶ ଇ-ରିଡର ନିମନ୍ତେ)", 32 | "format-epub-2": "EPUB 2 (ପୁରୁଣା, କେତେକ ପୁରୁଣା ଇ-ରିଡର ପାଇଁ ଉପଯୋଗୀ ହୋଇପାରେ)", 33 | "format-htmlz": "HTML", 34 | "format-mobi": "MOBI (Kindle ପାଇଁ)", 35 | "format-pdf-a4": "PDF - A4 ଆକାର", 36 | "format-pdf-a5": "PDF - A5 ଆକାର", 37 | "format-pdf-a6": "PDF - A6 ଆକାର", 38 | "format-pdf-letter": "PDF - ଆମେରିକା ଲେଟର ଆକାର", 39 | "format-rtf": "RTF", 40 | "format-txt": "ସାଦା ଲେଖା", 41 | "exception-invalid-format": "\"$1\" ଏକ ବୈଧ ଫର୍ମାଟ ନୁହେଁ । ବୈଧ ଫର୍ମାଟସବୁ ହେଲା: $2", 42 | "font-field-label": "ଫଣ୍ଟ", 43 | "no-font-option": "କୌଣସିଟି ନୁହେଁ (ଡିଭାଇସରେ ଯାହା ଅଛି ତାହାର ବ୍ୟବହାର)", 44 | "font-field-help": "$1ରେ ଥିବା ଫଣ୍ଟରୁ ବାଛନ୍ତୁ ।", 45 | "options-label": "ବିକଳ୍ପଗୁଡ଼ିକ", 46 | "credits-field-label": "ସମ୍ପାଦକ ସ୍ୱତ୍ତ୍ୱ କାଢ଼ନ୍ତୁ (ଚଞ୍ଚଳ ଡାଉନଲୋଡ଼)", 47 | "credits-default-message": "[ଅନୁରୋଧ ଅନୁସାରେ ଯୋଗଦାନକାରୀଙ୍କ ତାଲିକା ବାଦ ଦିଆଯାଇଛି ।]", 48 | "images-field-label": "ଛବି ପୂରାନ୍ତୁ ନାହିଁ", 49 | "nocache-field-label": "ସମସ୍ତ କ୍ୟାଚିଂକୁ ବାଇପାସ କରନ୍ତୁ (ମାନ୍ଦା କିନ୍ତୁ ଡିବଗିଂ ପାଇଁ ଉପଯୋଗୀ)", 50 | "export-button": "ଏକ୍ସପୋର୍ଟ", 51 | "cache-updated": "ଭାଷା ପାଇଁ କ୍ୟାସ ସତେଜ ହୋଇଛି: $1", 52 | "alert-close": "ବନ୍ଦକରନ୍ତୁ", 53 | "export-failed": "ଡାଉନଲୋଡ଼ ବିଫଳ ।", 54 | "exception-url-fetch-error": "URL ପୁନରୁଦ୍ଧାର କରିବାରେ ଅସମର୍ଥ: $1", 55 | "exception-rest-page-not-found": "'$1' ବହିଟି ମିଳିଲା ନାହିଁ ।", 56 | "learn-more": "ଅଧିକ ଜାଣନ୍ତୁ ।", 57 | "error-fix": "କୌଣସି ଅସୁବିଧା ହେଲେ ଦୟାକରି ଏହି ସୋପାନ ଅନୁସାରେ ଚେଷ୍ଟାକରନ୍ତୁ:", 58 | "error-fix-again": "ବହିଟି ଆଉଥରେ ଡାଉନଲୋଡ଼ କରିବା ପାଇଁ ଚେଷ୍ଟାକରନ୍ତୁ ।", 59 | "error-fix-epub": "ବହିଟି EPUB ଫାଇଲ ଭାବେ ଡାଉନଲୋଡ଼ କରିବା ପାଇଁ ଚେଷ୍ଟାକରନ୍ତୁ ।", 60 | "error-fix-nocredits": "ସ୍ୱତ୍ତ୍ୱ କାଢ଼ିବା ପାଇଁ 'ବିକଳ୍ପ'ରେ ଥିବା ଘର ଦେଖନ୍ତୁ", 61 | "error-fix-noimages": "ଛବି କାଢ଼ିବା ପାଇଁ 'ବିକଳ୍ପ'ରେ ଥିବା ଘର ଦେଖନ୍ତୁ ।", 62 | "error-breadcrumb": "ତ୍ରୁଟି: $1", 63 | "error-page-header": "ଏକ ଅସୁବିଧା ହେଲା", 64 | "error-page-details": "ଅସୁବିଧା ସବିଶେଷ: $1", 65 | "error-page-issue": "ଏହି ଅସୁବିଧା ପୁଣି ହେଲେ ଦୟାକରି $1 ।", 66 | "error-page-issue-link": "ଫାବ୍ରିକେଟରରେ ଅସୁବିଧା ଜଣାନ୍ତୁ", 67 | "onwikiconfig-failure": "$1ରୁ ଉଇକି ସଜାଣି ପୁନରୁଦ୍ଧାର କରିବାରେ ଅସମର୍ଥ", 68 | "exception-fetching-credits": "ସ୍ୱତ୍ତ୍ୱ ସୂଚନା ପାଇବାରେ ଅସୁବିଧା । ଦୟାକରି ଆଉଥରେ ଏକ୍ସପୋର୍ଟ କରିବା ପାଇଁ ଚେଷ୍ଟାକରନ୍ତୁ । ଅସୁବିଧା ଲାଗିରହିଲେ ସ୍ୱତ୍ତ୍ୱ କାଢ଼ିବା ପାଇଁ 'ବିକଳ୍ପ'ରେ ଥିବା ଘର ଦେଖନ୍ତୁ ।", 69 | "exception-book-conversion": "ବହିର ଫର୍ମାଟ ଏକ୍ସପୋର୍ଟ କରିବା ବେଳେ ଅସୁବିଧା ହେଲା ।", 70 | "epub-title-page": "ଶିରୋନାମ ପୃଷ୍ଠା", 71 | "epub-exported-date": "ଉଇକିପାଠାଗରରୁ $1 ଦିନ ଏକ୍ସପୋର୍ଟ ହୋଇଛି", 72 | "epub-about": "ବାବଦରେ" 73 | } 74 | -------------------------------------------------------------------------------- /i18n/pa.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Cabal", 5 | "Kuldeepburjbhalaike", 6 | "Satdeep gill" 7 | ] 8 | }, 9 | "app-title": "ਵਿਕੀਸਰੋਤ ਕਿਤਾਬ ਬਰਾਮਦ", 10 | "app-title-wikisource": "ਵਿਕੀਸਰੋਤ", 11 | "app-title-html": "$1 ਕਿਤਾਬ ਬਰਾਮਦ", 12 | "app-subtitle": "ਵਿਕੀਸੋਰਤਾਂ ਤੋਂ ਕਿਤਾਬਾਂ ਨੂੰ ਕਈ ਵੱਖ-ਵੱਖ ਫਾਈਲ ਰੂਪ-ਰੇਖਾ ਵਿੱਚ ਬਰਾਮਦ ਕਰੋ।", 13 | "commit-label": "ਵਚਨਬੱਧਤਾ: $1", 14 | "source-link": "Github 'ਤੇ", 15 | "docs-link": "ਬਹੁਭਾਸ਼ਾਈ ਵਿਕੀਸਰੋਤ ਉੱਤੇ", 16 | "statistics-link": "ਅੰਕੜੇ", 17 | "issue-button": "ਸਮੱਸਿਆ ਬਣਾਓ", 18 | "back-to-wikisource": "ਵਿਕੀਸਰੋਤ ’ਤੇ ਵਾਪਸ ਜਾਓ", 19 | "export-book": "ਇੱਕ ਕਿਤਾਬ ਬਰਾਮਦ ਕਰੋ", 20 | "lang-field-label": "ਵਿਕੀਸਰੋਤ", 21 | "lang-field-help": "ਵਿਕੀਸੋਰਤ ਜਿਸ ਤੋਂ ਕਿਤਾਬ ਨੂੰ ਬਰਾਮਦ ਕਰਨਾ ਹੈ।", 22 | "title-field-label": "ਸਿਰਲੇਖ", 23 | "title-field-help": "ਬਰਾਮਦ ਕਰਨ ਲਈ ਕਿਤਾਬ ਜਾਂ ਭਾਗ ਦਾ ਸਿਰਲੇਖ।", 24 | "format-field-label": "ਫ਼ਾਈਲਾਂ ਦੇ ਰੂਪ", 25 | "format-txt": "ਆਮ ਲਿਖਤ", 26 | "font-field-label": "ਫ਼ੌਂਟ", 27 | "no-font-option": "ਕੋਈ ਨਹੀਂ (ਜੰਤਰ ਦੇ ਮੂਲ ਦੀ ਵਰਤੋਂ ਕਰੋ)", 28 | "options-label": "ਚੋਣਾਂ", 29 | "images-field-label": "ਚਿੱਤਰ ਸ਼ਾਮਲ ਨਾ ਕਰੋ", 30 | "export-button": "ਬਰਾਮਦ", 31 | "alert-close": "ਬੰਦ ਕਰੋ", 32 | "learn-more": "ਹੋਰ ਸਿੱਖੋ", 33 | "error-fix-nocredits": "ਸੇਹਰਾ ਹਟਾਉਣ ਲਈ 'ਚੋਣਾਂ' ਵਿੱਚ ਡੱਬੇ ਉੱਤੇ ਨਿਸ਼ਾਨ ਲਾਓ", 34 | "error-fix-noimages": "ਤਸਵੀਰਾਂ ਹਟਾਉਣ ਲਈ 'ਚੋਣਾਂ' ਵਿੱਚ ਡੱਬੇ ਉੱਤੇ ਨਿਸ਼ਾਨ ਲਾਓ।", 35 | "error-breadcrumb": "ਗਲਤੀ $1", 36 | "error-page-header": "ਇੱਕ ਗਲਤੀ ਆਈ ਹੈ", 37 | "error-page-details": "ਗਲਤੀ ਵੇਰਵੇ: $1", 38 | "error-page-issue": "ਜੇਕਰ ਇਹ ਗਲਤੀ ਬਣੀ ਰਹਿੰਦੀ ਹੈ, ਤਾਂ ਕਿਰਪਾ ਕਰਕੇ $1।", 39 | "error-page-issue-link": "ਫੈਬਰੀਕੇਟਰ ਉੱਤੇ ਇੱਕ ਮੁੱਦੇ ਦੀ ਇਤਲਾਹ ਦਿਓ", 40 | "epub-title-page": "ਸਿਰਲੇਖ ਸਫ਼ਾ", 41 | "epub-exported-date": "$1 ਤੇ ਵਿਕੀਸੋਰਤ ਤੋਂ ਬਰਾਮਦ ਕੀਤਾ ਗਿਆ", 42 | "epub-about": "ਬਾਬਤ", 43 | "breadcrumbs-home-link": "ਘਰ", 44 | "breadcrumbs-stats-link": "ਅੰਕੜੇ", 45 | "recently-popular-heading": "ਹਾਲ ਹੀ ਵਿੱਚ ਮਸ਼ਹੂਰ", 46 | "change-lang": "ਭਾਸ਼ਾ ਬਦਲੋ" 47 | } 48 | -------------------------------------------------------------------------------- /i18n/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Matlin", 5 | "Thoji" 6 | ] 7 | }, 8 | "app-title": "Eksport książek z Wikiźródeł", 9 | "app-title-html": "Eksport książki $1", 10 | "app-subtitle": "Eksport książek z Wikiźródeł w wielu różnych formatach plików.", 11 | "logo-alt-text": "Logo Wikiźródeł, niebiesko-biała ilustracja góry lodowej.", 12 | "footer-version": "To jest $1 w wersji $2.", 13 | "version": "wersja $1", 14 | "license-link": "GPL v2 lub nowsza", 15 | "source-and-docs": "Dostępny jest kod źródłowy $1 i trochę dokumentacji $2.", 16 | "source-link": "na Githubie", 17 | "docs-link": "na wielojęzycznych Wikiźródłach", 18 | "statistics": "Przewidziano również $1.", 19 | "statistics-link": "statystyki", 20 | "issues": "Błędy proszę zgłaszać w ramach projektu $1 na Phabricatorze.", 21 | "issue-button": "Utwórz problem", 22 | "back-to-wikisource": "Powrót do Wikiźródeł", 23 | "lang-field-label": "Wikiźródła", 24 | "title-field-label": "Tytuł", 25 | "title-field-help": "Tytuł książki lub sekcji do wyeksportowania.", 26 | "format-field-label": "Format pliku", 27 | "format-epub-3": "EPUB 3 (dla większości czytników)", 28 | "format-epub-2": "EPUB 2 (przestarzały, może być przydatny w przypadku bardzo starych czytników e-booków)", 29 | "format-htmlz": "HTML", 30 | "format-rtf": "RTF", 31 | "format-txt": "Zwykły tekst", 32 | "font-field-label": "Czcionka", 33 | "export-button": "Eksport", 34 | "epub-title-page": "Strona tytułowa", 35 | "epub-exported-date": "Pobrano z Wikiźródeł dnia $1", 36 | "epub-about": "O tej publikacji" 37 | } 38 | -------------------------------------------------------------------------------- /i18n/sje.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Olve Utne" 5 | ] 6 | }, 7 | "app-title": "Wikisource-girrjeekspårrtå", 8 | "app-title-html": "$1 Girrjeekspårrtå", 9 | "footer-version": "Dát lä $1, versjuvdna $2.", 10 | "version": "versjuvdna $1", 11 | "source-link": "Github sinne", 12 | "statistics-link": "Statistijjka", 13 | "back-to-wikisource": "Ruoptojt Wikisource gugu", 14 | "export-book": "Eksporteri girjev", 15 | "lang-field-label": "Wikigálldo", 16 | "title-field-label": "Girrjenamma", 17 | "format-field-label": "Fijjlaformáhtta", 18 | "format-htmlz": "HTML", 19 | "format-pdf-a4": "PDF - A4-sturrudak", 20 | "format-pdf-a5": "PDF - A5-sturrudak", 21 | "format-pdf-a6": "PDF - A6-sturrudak", 22 | "format-pdf-letter": "PDF - \"US letter\" sturrudak", 23 | "format-rtf": "RTF", 24 | "font-field-label": "Fånntå", 25 | "export-button": "Eksporteri", 26 | "learn-more": "Liera ienabuv.", 27 | "epub-about": "Birra" 28 | } 29 | -------------------------------------------------------------------------------- /i18n/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Jaroslav.micek", 5 | "Yardom78" 6 | ] 7 | }, 8 | "app-title": "Export kníh z Wikizdrojov", 9 | "app-title-html": "Exportovanie knihy $1", 10 | "app-subtitle": "Export kníh z Wikisources v rozličných formátoch.", 11 | "logo-alt-text": "Logo Wikizdrojov, modrobiela ilustrácia ľadovca.", 12 | "footer-version": "Toto je $1 verzia $2.", 13 | "version": "verzia $1", 14 | "commit-label": "Commit: $1", 15 | "licensed-under": "Licencované cez $1.", 16 | "license-link": "GPL v2 alebo neskoršie", 17 | "source-and-docs": "Zdrojový kód je dostupný na $1 a nejaká dokumentácia na $2.", 18 | "source-link": "na Github", 19 | "docs-link": "na Multilingual Wikisource", 20 | "statistics": "Štatistiky na $1.", 21 | "statistics-link": "štatistiky", 22 | "issues": "Prosím nahláste chybu $1", 23 | "issue-button": "Vytvoriť report", 24 | "back-to-wikisource": "Späť na Wikizdroje", 25 | "previous-alt-text": "Šípka nasmerovaná smerom vzad, ktorá oznamuje, že odkaz Vás vráti späť na Wikizdroje.", 26 | "export-book": "Exportovať knihu", 27 | "lang-field-label": "Wikizdroj", 28 | "lang-field-help": "Wikizdroj z ktorého chcete exportovať knihu.", 29 | "title-field-label": "Názov", 30 | "title-field-help": "Názov knihy alebo odseku na export.", 31 | "format-field-label": "Formát súboru", 32 | "format-field-help": "(názov formátu súboru)", 33 | "format-epub-3": "EPUB 3 (pre väčšinu čítačiek elektronických kníh)", 34 | "format-epub-2": "EPUB 2 (zastaraný, ale môže byť vhodný pre staré čítačky)", 35 | "format-htmlz": "HTML", 36 | "format-mobi": "MOBI (pre Kindles)", 37 | "format-pdf-a4": "PDF - veľkosť A4", 38 | "format-pdf-a5": "PDF - veľkosť A5", 39 | "format-pdf-a6": "PDF - veľkosť A6", 40 | "format-pdf-letter": "PDF - US veľkosť písma", 41 | "format-rtf": "RTF", 42 | "format-txt": "Čistý text", 43 | "exceeded-rate-limitation": "Vo veľmi krátkom čase ste zadali príliš veľa požiadaviek. Pred opätovným načítaním tohto nástroja prosím počkajte {{PLURAL:$1|minútu|minúty|minút}}.", 44 | "exception-invalid-format": "\"$1\" nie je platný formát. Platné formáty sú: $2", 45 | "font-field-label": "Písmo", 46 | "no-font-option": "Žiadne (použiť predvolené nastavenie zariadenia)", 47 | "font-field-help": "Možné dostupné písma pre výber: $1", 48 | "options-label": "Možnosti", 49 | "credits-field-label": "Nezahrnúť detaily redaktorov (rýchlejšie sťahovanie)", 50 | "credits-default-message": "[Zoznam prispievateľov bol vynechaný tak ako bolo požadované.]", 51 | "images-field-label": "Nepridať obrázky", 52 | "nocache-field-label": "Obísť cashing (pomalšie ale vhodné pre vylaďovanie)", 53 | "export-button": "Export", 54 | "cache-updated": "Cache jazyka $1 bol aktualizovaný", 55 | "alert-close": "Zavrieť", 56 | "export-failed": "Sťahovanie neúspešné.", 57 | "exception-url-fetch-error": "Neúspešné načítanie URL: $1", 58 | "exception-rest-page-not-found": "Knihu $1 nemožno nájsť.", 59 | "learn-more": "Zistiť viac.", 60 | "error-blocked-1": "Vaša IP adresa je momentálne blokovaná.", 61 | "error-blocked-2": "Pre vyhnutie sa zneužitia automatizácie, WS Export zakazuje prístup z odhlásených účtov, ktorí sú globálne blokovaní na stránkach Wikimédia.", 62 | "error-fix": "Prosím skúste tieto kroky ak ste narazili na chybu:", 63 | "error-fix-again": "Skúste knihu stiahnuť znovu.", 64 | "error-fix-epub": "Skúste knihu stiahnuť vo formáte EPUB.", 65 | "error-fix-nocredits": "V nastaveniach zaškrtnite okienko pre odstránenie údajov o zásluhách", 66 | "error-fix-noimages": "V nastaveniach zaškrtnite okienko pre odstránenie obrázkov.", 67 | "error-breadcrumb": "Chyba $1", 68 | "error-login": "Prosím prihláste sa do WS Export pre pokračovanie.", 69 | "error-page-header": "Vyskytla sa chyba", 70 | "error-page-details": "Podrobnosti o chybe: $1", 71 | "error-page-issue": "AK chyba pretrváva, prosím $1.", 72 | "error-page-issue-link": "nahláste problém na Phabricator", 73 | "onwikiconfig-failure": "Nie je možné načítať konfiguráciu z: $1", 74 | "exception-fetching-credits": "Pri načítavaní informácií o zásluhách došlo k chybe. Prosím skúste exportovať znovu. Ak bude chyba pretrvávať, tak v možnostiach zaškrtnite políčko pre odstránenie týchto informácií.", 75 | "exception-book-conversion": "Pri konverzii formátu knihy došlo k chybe.", 76 | "epub-title-page": "Titulná stránka", 77 | "epub-exported-date": "Dátum exportovania z Wikizdrojov: $1", 78 | "epub-about": "O aplikácii" 79 | } 80 | -------------------------------------------------------------------------------- /i18n/skr-arab.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Saraiki" 5 | ] 6 | }, 7 | "app-title": "وکی ماخذ کتاب ایکسپورٹ", 8 | "app-title-wikisource": "وکی ماخذ", 9 | "app-title-html": "$1 کتاب ایکسپورٹ", 10 | "version": "ورژن $1", 11 | "source-link": "گٹ ہب تے", 12 | "statistics-link": "شماريات", 13 | "back-to-wikisource": "وکی ماخذ تے واپس", 14 | "export-book": "کتاب ایکسپورٹ کرو", 15 | "lang-field-label": "وکی ماخذ", 16 | "title-field-label": "عنوان", 17 | "format-field-label": "فائل فارمیٹ", 18 | "format-htmlz": "ایچ ٹی ایم ایل", 19 | "format-pdf-a4": "پی ڈی ایف ـ A4 سائز", 20 | "format-pdf-a5": "پی ڈی ایف ـ A5 سائز", 21 | "format-pdf-a6": "پی ڈی ایف ـ A6 سائز", 22 | "format-pdf-letter": "پی ڈی ایف ـ امریکی لیٹر سائز", 23 | "format-rtf": "آرٹی ایف", 24 | "format-txt": "سادہ عبارت", 25 | "font-field-label": "فونٹ", 26 | "options-label": "اختیارات", 27 | "images-field-label": "تصویراں وچ شامل نہ کرو", 28 | "export-button": "ٻاہر بھیڄو", 29 | "alert-close": "بند کرو", 30 | "export-failed": "ڈاؤن لوڈ وچ ناکامی تھئی۔", 31 | "learn-more": "ٻیا سِکھو۔", 32 | "error-breadcrumb": "نقص $1", 33 | "error-page-header": "رپھڑ پئے ڳیا", 34 | "error-page-details": "خرابی تفصیلاں:$1", 35 | "epub-title-page": "عنوان ورقہ", 36 | "epub-about": "تعارف", 37 | "breadcrumbs-home-link": "گھر", 38 | "breadcrumbs-stats-link": "شماريات" 39 | } 40 | -------------------------------------------------------------------------------- /i18n/sr-ec.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Milicevic01" 5 | ] 6 | }, 7 | "app-title": "Викизворник Извоз књиге", 8 | "app-title-wikisource": "Викизворник", 9 | "app-subtitle": "Извоз књига из Викизворника у више различитих формата.", 10 | "source-and-docs": "Изворни код је доступан $1 и одређена документација $2.", 11 | "source-link": "на Github-у", 12 | "docs-link": "на Вишејезичном Викизворнику", 13 | "statistics-link": "статистика", 14 | "issue-button": "Пријави баг", 15 | "back-to-wikisource": "Назад на Викизворник", 16 | "export-book": "Извоз књиге", 17 | "lang-field-label": "Викизворник", 18 | "lang-field-help": "Викизворник са којег се извози књига.", 19 | "title-field-label": "Наслов", 20 | "title-field-help": "Наслов књиге или одељка за извоз.", 21 | "format-field-label": "Формат датотеке", 22 | "format-pdf-a4": "PDF - А4", 23 | "format-pdf-a5": "PDF - А5", 24 | "format-pdf-a6": "PDF - А6", 25 | "format-txt": "Обичан текст", 26 | "font-field-label": "Фонт", 27 | "font-field-help": "Изаберите један од $1 доступних фонтова.", 28 | "options-label": "Опције", 29 | "images-field-label": "Без слика", 30 | "export-button": "Извези", 31 | "alert-close": "Затвори", 32 | "export-failed": "Преузимање није успело.", 33 | "exception-rest-page-not-found": "Страница „$1” није пронађена.", 34 | "learn-more": "Сазнајте више.", 35 | "error-blocked-1": "Ваша IP адреса је тренутно блокирана.", 36 | "error-breadcrumb": "Грешка $1", 37 | "error-page-details": "Детаљи грешке: $1", 38 | "epub-exported-date": "Извезено са Викизворника $1", 39 | "epub-about": "О", 40 | "breadcrumbs-home-link": "Почетна", 41 | "breadcrumbs-stats-link": "Статистике", 42 | "change-lang": "Промени језик" 43 | } 44 | -------------------------------------------------------------------------------- /i18n/su.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Kandar" 5 | ] 6 | }, 7 | "app-title": "Éxpor Pustaka Wikisource", 8 | "app-title-html": "$1 Éxpor Pustaka", 9 | "app-subtitle": "Éxpor kapustakaan ti Wikisource dina rupa-rupa pormat berkas.", 10 | "logo-alt-text": "Logo Wikisource, ilustrasi gunung és kelir paul jeung bodas.", 11 | "footer-version": "Ieu minangka $1 pérsi $2.", 12 | "version": "Pérsi $1", 13 | "commit-label": "Komit: $1", 14 | "licensed-under": "Dilisénsian $1.", 15 | "license-link": "GPL v2 atawa sanggeusna", 16 | "source-and-docs": "Kode sumberna sayaga $1 katut sababaraha dokuméntasi $2.", 17 | "source-link": "di Github", 18 | "docs-link": "di Wikisource multibasa", 19 | "statistics": "Sababaraha $1 ogé disayagakeun.", 20 | "statistics-link": "Statistik", 21 | "issues": "Mangga laporkeun isu dina proyék $1 di Phabricator.", 22 | "issue-button": "Jieun isu", 23 | "back-to-wikisource": "Balik ka Wikisource", 24 | "previous-alt-text": "Ikon panah mundur anu nandakeun yén tutumbuna malikkeun ka Wikisource.", 25 | "export-book": "Éxpor pustaka", 26 | "lang-field-label": "Wikisource", 27 | "lang-field-help": "Wikisource sumber ngéxpor pustakana.", 28 | "title-field-label": "Judul", 29 | "title-field-help": "Judul pustaka atawa bagian anu diéxpor.", 30 | "format-field-label": "Pormat berkas", 31 | "format-epub-3": "EPUB 3 (pikeun kalolobaan pamaca-élek)", 32 | "format-epub-2": "EPUB 2 (geus teu dipaké, bisa pikeun ereader heubeul)", 33 | "format-htmlz": "HTML", 34 | "format-mobi": "MOBI (pikeun Kindles)", 35 | "format-pdf-a4": "PDF - ukuran A4", 36 | "format-pdf-a5": "PDF - ukuran A5", 37 | "format-pdf-a6": "PDF - ukuran A6", 38 | "format-pdf-letter": "PDF - ukuran US letter", 39 | "format-rtf": "RTF", 40 | "format-txt": "téx polos", 41 | "exceeded-rate-limitation": "Anjeun loba teuing rekés dina waktu anu sakeudeung. Tungguan $1 {{PLURAL:$1|menit|menit}} saméméh ngamuat deui ieu parabot, atawa login pikeun nyingkahan kieu deui.", 42 | "exception-invalid-format": "\"$1\" lain pormat anu sah. Pormat anu sah: $2", 43 | "font-field-label": "Aksara", 44 | "no-font-option": "Euweuh (pake baku piranti)", 45 | "font-field-help": "Pilih ti $1 aksara anu sayaga.", 46 | "options-label": "Pilihan", 47 | "credits-field-label": "Iwalkeun krédit éditor (ngundeur leuwih téréh)", 48 | "credits-default-message": "[Béréndélan kontributor teu diasupkeun, luyu jeung rekésna.]", 49 | "images-field-label": "Gambar iwalkeun", 50 | "nocache-field-label": "Tarabas sakabéh caching (leuwih laun tapi matih pikeun debugging)", 51 | "export-button": "Éxpor", 52 | "cache-updated": "Cache geus disegerkeun pikeun basa: $1", 53 | "alert-close": "Tutup", 54 | "export-failed": "Ngundeur gagal.", 55 | "exception-url-fetch-error": "Teu bisa muka URL: $1", 56 | "exception-rest-page-not-found": "Pustaka '$1' teu kapanggih.", 57 | "learn-more": "Lenyepan.", 58 | "error-blocked-1": "Alamat IP anjeun keur dipeungpeuk.", 59 | "error-blocked-2": "Pikeun nyegah otomasi anu ngaruksak, Expor WS nolak aksés ti pamaké anu geus kaluar log anu sacara global dipeungpeuk di sakur wiki Wikimédia.", 60 | "error-fix": "Mangga cobian léngkah ieu upami anjeun mendakan kasalahan:", 61 | "error-fix-again": "Coba undeur deui bukuna.", 62 | "error-fix-epub": "Coba undeur buku salaku berkas EPUB.", 63 | "error-fix-nocredits": "Céntang kotak dina 'Pilihan' pikeun ngahapus pangajén", 64 | "error-fix-noimages": "Céntang kotak dina 'Pilihan' pikeun ngahapus gambar.", 65 | "error-breadcrumb": "Kasalahan $1", 66 | "error-login": "Mangga login ka WS Export pikeun nuluykeun." 67 | } 68 | -------------------------------------------------------------------------------- /i18n/ta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Balajijagadesh", 5 | "Fahimrazick" 6 | ] 7 | }, 8 | "app-title": "விக்கிமூலம் நூல் ஏற்றுமதி", 9 | "app-title-html": "$1 நூல் ஏற்றுமதி", 10 | "version": "திருத்தம் $1", 11 | "statistics-link": "புள்ளிவிவரங்கள்", 12 | "back-to-wikisource": "விக்கிமூலத்திற்கு மீண்டும் செல்க", 13 | "export-book": "ஒரு நூலை ஏற்றுமதி செய்க", 14 | "lang-field-label": "விக்கிமூலம்", 15 | "title-field-label": "தலைப்பு", 16 | "title-field-help": "ஏற்றுமதி செய்யப்பட வேண்டிய நூல் அல்லது பிரிவின் தலைப்பு.", 17 | "format-field-label": "கோப்பு வடிவமைப்பு", 18 | "format-txt": "எளிய உரை", 19 | "font-field-label": "எழுத்துரு", 20 | "font-field-help": "உள்ள $1 எழுத்துருக்களில் தேர்ந்தெடுக்கவும்.", 21 | "options-label": "விருப்பத்தேர்வுகள்", 22 | "credits-field-label": "பங்களித்தவர் விவரங்களை விட்டுவிடுக (விரைவான பதிவிரக்கம்)", 23 | "images-field-label": "படங்களை சேர்க்க வேண்டாம்", 24 | "export-button": "ஏற்றுமதி செய்க", 25 | "alert-close": "மூடுக", 26 | "export-failed": "கோப்பு பதிவிறக்கம் தோல்வியுற்றது.", 27 | "exception-rest-page-not-found": "$1 நூல் காணப்படவில்லை", 28 | "learn-more": "மேலும் அறிந்துகொள்ள.", 29 | "error-fix-again": "நூலை பதிவிறக்கம் செய்ய மீண்டும் முயற்சிக்கவும்.", 30 | "error-breadcrumb": "பிழை $1", 31 | "error-page-header": "ஒரு பிழை ஏற்பட்டது", 32 | "error-page-details": "பிழை விவரங்கள்: $1", 33 | "error-page-issue": "இப்பிழை தொடர்ந்தால், தயவு செய்து $1.", 34 | "exception-book-conversion": "நூலின் வடிவத்தை மாற்றும் பொழுது பிழை ஏற்பட்டுள்ளது.", 35 | "epub-title-page": "தலைப்புப் பக்கம்", 36 | "epub-exported-date": "$1 அன்று விக்கிமூலத்தில் இருந்து பதிவிறக்கப்பட்டது", 37 | "epub-about": "இதைப் பற்றி", 38 | "breadcrumbs-home-link": "முகப்பு", 39 | "breadcrumbs-stats-link": "புள்ளிவிவரங்கள்", 40 | "recently-popular-heading": "அண்மையிற் புகழ் பெற்றவை", 41 | "recently-popular-subtext": "சென்ற மூன்று மாதங்களிலிருந்து ஆகவும் புகழ் பெற்ற பதிவிறக்கங்கள்" 42 | } 43 | -------------------------------------------------------------------------------- /i18n/te.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Chaduvari", 5 | "Kasyap", 6 | "Veeven" 7 | ] 8 | }, 9 | "app-title": "వికీసోర్స్ పుస్తకం ఎగుమతి", 10 | "app-title-html": "$1 పుస్తకం ఎగుమతి", 11 | "app-subtitle": "వికీసోర్సు నుండి పుస్తకాలను అనేక ఆకృతుల్లో ఎగుమతి చేసుకోండి.", 12 | "logo-alt-text": "వికీసోర్స్ లోగో, నీలం, తెలుపు రంగుల్లో ఉన్న మంచుకొండ బొమ్మ", 13 | "footer-version": "ఇది $1 కూర్పు $2.", 14 | "version": "కూర్పు $1", 15 | "commit-label": "కమిట్: $1", 16 | "licensed-under": "ఇది $1 లైసెన్సుకు లోబడి ఉంది.", 17 | "license-link": "GPL v2 లేదా ఆపైన", 18 | "source-and-docs": "సోర్సు కోడు $1 వద్ద, కొంత డాక్యుమెంటేషను $2 వద్ద ఉన్నాయి.", 19 | "source-link": "Github లో", 20 | "docs-link": "బహుభాషా వికీసోర్సులో", 21 | "statistics": "కొన్ని $1 కూడా ఉన్నాయి.", 22 | "statistics-link": "గణాంకాలు", 23 | "issues": "సమస్యలను ఫ్యాబ్రికేటరు లోని $1 ప్రాజెక్టులో నివేదించండి.", 24 | "issue-button": "సమస్య నివేదికను సృష్టించండి", 25 | "back-to-wikisource": "తిరిగి వికీసోర్స్‌కు", 26 | "previous-alt-text": "వెనక్కి తిరిగి ఉన్న బాణం గుర్తు, ఆ లింకు తిరిగి వికీసోర్సుకు తీసుకు వెళ్తుందని అర్థం.", 27 | "export-book": "పుస్తకాన్ని ఎగుమతి చెయ్యండి", 28 | "lang-field-label": "వికీసోర్స్", 29 | "lang-field-help": "పుస్తకాన్ని ఏ వికీసోర్సు నుండి చెయ్యాలో అది.", 30 | "title-field-label": "శీర్షిక", 31 | "title-field-help": "ఎగుమతి చెయ్యాల్సిన పుస్తకం పేరు లేదా విభాగం.", 32 | "format-field-label": "ఫైలు ఆకృతి", 33 | "format-epub-3": "EPUB 3 (చాలామంది ఈ-రీడర్లకు)", 34 | "format-epub-2": "EPUB 2 (వాడుకలో లేదు, చాలా పాత ఈరీడర్లకు సరిపోవచ్చు)", 35 | "format-htmlz": "HTML", 36 | "format-mobi": "MOBI (కిండిల్ లకు)", 37 | "format-pdf-a4": "PDF - A4 సైజు", 38 | "format-pdf-a5": "PDF - A5 సైజు", 39 | "format-pdf-a6": "PDF - A6 సైజు", 40 | "format-pdf-letter": "PDF - అమెరికా ఉత్తరం సైజు", 41 | "format-rtf": "RTF", 42 | "format-txt": "సాదా పాఠ్యం", 43 | "exceeded-rate-limitation": "కొద్ది సమయం లోనే అనేక అభ్యర్థనలు చేస్తున్నారు. ఈ పరికరాన్ని రీలోడు చెయ్యడానికి $1 {{PLURAL:$1|నిమిషం|నిమిషాలు}} ఆగండి. లేదా ఇలా మళ్ళీ జరక్కుండా ఉండాలంటే లాగినవండి.", 44 | "exception-invalid-format": "\"$1\" సరైన ఆకృతి కాదు. చెల్లే ఆకృతులు: $2", 45 | "font-field-label": "ఖతి", 46 | "no-font-option": "ఏదీకాదు (డివైసు డిఫాల్టును వాడు)", 47 | "font-field-help": "అందుబాటులో ఉన్న $1 ఖతుల్లోంచి ఎంచుకోండి.", 48 | "options-label": "ఎంపికలు", 49 | "credits-field-label": "ఎడిటరు శ్రేయస్సులను చేర్చవద్దు (వేగంగా దించుకునేందుకు)", 50 | "credits-default-message": "[అడిగిన విధంగా పని చేసిన వారి జాబితాను చేర్చలేదు.]", 51 | "images-field-label": "బొమ్మలను చేర్చవద్దు", 52 | "nocache-field-label": "కాషెను బైపాసు చెయ్యి (నెమ్మదిగా ఉంటుంది గానీ, డీబగ్గింగుకు ఉపయోగం)", 53 | "export-button": "ఎగుమతి చెయ్యి", 54 | "cache-updated": "ఈ భాషకు కాషెను తాజాకరించాం: $1", 55 | "alert-close": "మూసివేయి", 56 | "export-failed": "దింపుకోలు విఫలమైంది.", 57 | "exception-url-fetch-error": "ఈ URL ను తేలేకపోయాం: $1", 58 | "exception-rest-page-not-found": "'$1' పుస్తకం కనబడలేదు.", 59 | "learn-more": "మరింత తెలుసుకోండి.", 60 | "error-blocked-1": "మీ ఐపీ చిరునామా ప్రస్తుతం నిరోధంలో ఉంది.", 61 | "error-blocked-2": "చెడగొట్టే ఆటోమేషనుతో చేసే దుశ్చర్యను నివారించేందుకు గాను WS Export, వికీమీడియా వికీల్లో సార్వత్రికంగా నిరోధింపబడిన లాగౌటైన వాడుకరుల ప్రవేశాన్ని అడ్డుకుంటుంది.", 62 | "error-fix": "ఏదైనా లోపం ఎదురైతే, ఈ అంగలను ప్రయత్నించండి:", 63 | "error-fix-again": "పుస్తకాన్ని దించుకునే ప్రయత్నం మళ్ళీ చెయ్యండి.", 64 | "error-fix-epub": "పుస్తకాన్ని EPUB ఫైలుగా దించుకునే ప్రయత్నం చెయ్యండి.", 65 | "error-fix-nocredits": "శ్రేయస్సును తీసెయ్యమనే అనే పెట్టెలో టిక్కు పెట్టండి", 66 | "error-fix-noimages": "బొమ్మలను తీసెయ్యమనే పెట్టెలో టిక్కు పెట్టండి", 67 | "error-breadcrumb": "లోపం $1", 68 | "error-login": "కొనసాగేందుకు WS Export లోకి లాగినవండి.", 69 | "error-page-header": "లోపం జరిగింది", 70 | "error-page-details": "లోపం వివరాలు: $1", 71 | "error-page-issue": "ఈ లోపం ఇలాగే ఉంటే, $1", 72 | "error-page-issue-link": "ఫ్యాబ్రికేటరులో సమస్యను నివేదించండి", 73 | "onwikiconfig-failure": "ఇక్కడి నుండి ఆన్-వికీ కాన్ఫిగరేషన్ను తేలేకపోయాం: $1", 74 | "exception-fetching-credits": "శ్రేయస్సు సమాచారాన్ని తేవడంలో లోపం. మళ్ళీ ఎగుమతి చేసేందుకు ప్రయత్నించండి. ఈ లోపం ఇలాగే ఉంటే, శ్రేయస్సులను తీసెయ్యమనే పెట్టెలో టిక్కు పెట్టండి.", 75 | "exception-book-conversion": "పుస్తకం ఆకృతిని మార్చడంలో లోపం.", 76 | "epub-title-page": "శీర్షిక పేజీ", 77 | "epub-exported-date": "$1 న వికీసోర్సు నుండి ఎగుమతి చేసారు", 78 | "epub-about": "గురించి" 79 | } 80 | -------------------------------------------------------------------------------- /i18n/vec.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [] 4 | }, 5 | "epub-title-page": "Titolo", 6 | "epub-exported-date": "Esportà da Wikisource el $1", 7 | "epub-about": "Informassion" 8 | } 9 | -------------------------------------------------------------------------------- /i18n/xmf.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Narazeni" 5 | ] 6 | }, 7 | "app-title": "წინგეფიშ ექსპორტი ვიკითეკაშე", 8 | "app-title-html": "$1-შე წინგიშ ექსპორტი", 9 | "app-subtitle": "ვიკითეკაშე წინგეფიშ ექსპორტი შხვადოშხვა ფაილიშ ფორმატით.", 10 | "logo-alt-text": "ვიკითეკაშ ლოგო, აისბერგიშ ლენი დო ჩე ილუსტრაცია.", 11 | "footer-version": "თენა რე $2 ვერსიაშ $1", 12 | "version": "ვერსია $1", 13 | "commit-label": "კომიტ: $1", 14 | "statistics-link": "სტატისტიკა", 15 | "title-field-label": "დუდჯოხო", 16 | "format-field-label": "ფაილიშ ფორმატი", 17 | "format-epub-3": "EPUB 3 (უმენტაშ E-reader-შო)", 18 | "format-htmlz": "HTML", 19 | "format-mobi": "MOBI (Kindle-შო)", 20 | "format-pdf-a4": "PDF - A4 ზჷმა", 21 | "format-pdf-a5": "PDF - A5 ზჷმა", 22 | "format-pdf-a6": "PDF - A6 ზჷმა", 23 | "format-txt": "რჩქვანელობური ტექსტი", 24 | "font-field-label": "შრიფტი", 25 | "options-label": "პარამეტრეფი", 26 | "images-field-label": "სურათეფიშ უმუშო", 27 | "export-button": "ექსპორტი", 28 | "alert-close": "კილუა", 29 | "learn-more": "ქიგეგით უმოსი.", 30 | "error-blocked-1": "თქვანი IP მიოწურაფუ ასე ბლოკირი რე." 31 | } 32 | -------------------------------------------------------------------------------- /i18n/zh-hans.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Anterdc99", 5 | "Crowley666", 6 | "FakeGreenHand", 7 | "GuoPC", 8 | "Hmgrmb", 9 | "LNDDYL", 10 | "LittlePaw365", 11 | "Mishidexfc", 12 | "Shizhao", 13 | "Si109", 14 | "StarrySky", 15 | "Zhang8569" 16 | ] 17 | }, 18 | "app-title": "维基文库电子书导出", 19 | "app-title-wikisource": "维基文库", 20 | "app-title-html": "$1电子书导出", 21 | "app-subtitle": "从维基文库以多种不同文件格式导出电子书。", 22 | "logo-alt-text": "维基文库徽标,一个蓝白相间的冰山插图。", 23 | "footer-version": "这是$1版本$2", 24 | "version": "版本$1", 25 | "commit-label": "提交:$1", 26 | "licensed-under": "以$1进行许可。", 27 | "license-link": "GPL v2或更高版本", 28 | "source-and-docs": "源代码位于$1,一些文档位于$2。", 29 | "source-link": "在Github上", 30 | "docs-link": "在多语言维基文库", 31 | "statistics": "同时提供了一些$1", 32 | "statistics-link": "统计信息", 33 | "issues": "请在 Phabricator 的$1项目下报告问题。", 34 | "issue-button": "创建问题", 35 | "back-to-wikisource": "返回到维基文库", 36 | "previous-alt-text": "面向后方的箭头图标表示链接返回到维基文库。", 37 | "export-book": "导出电子束", 38 | "lang-field-label": "维基文库", 39 | "lang-field-help": "要导出电子书的维基文库。", 40 | "title-field-label": "标题", 41 | "title-field-help": "要导出的书或章节标题", 42 | "format-field-label": "文件格式", 43 | "format-epub-3": "EPUB 3(适用于大多数电子书阅读器)", 44 | "format-epub-2": "EPUB 2(已弃用,可能对一些非常老旧的电子书阅读器有用)", 45 | "format-htmlz": "HTML", 46 | "format-mobi": "MOBI(适用于Kindle)", 47 | "format-pdf-a4": "PDF-A4尺寸", 48 | "format-pdf-a5": "PDF - A5大小", 49 | "format-pdf-a6": "PDF-A6尺寸", 50 | "format-pdf-letter": "PDF - 美国信纸大小", 51 | "format-rtf": "RTF", 52 | "format-txt": "纯文本", 53 | "exceeded-rate-limitation": "您在短时间内发出太多请求。请等待 $1 {{PLURAL:$1|分钟}}后再重新加载此工具,或者通过登录来避免再次发生这种情况。", 54 | "exception-invalid-format": "\"$1\" 不是一个有效的格式。有效的格式为: $2", 55 | "font-field-label": "字体", 56 | "no-font-option": "无(使用设备默认值)", 57 | "font-field-help": "从$1可用的字体中选择", 58 | "options-label": "选项", 59 | "credits-field-label": "排除编者署名(更快下载)", 60 | "credits-default-message": "[应要求省略了贡献者名单。]", 61 | "images-field-label": "不包括图像", 62 | "nocache-field-label": "绕过所有缓存(速度较慢但对调试有用)", 63 | "export-button": "导出", 64 | "cache-updated": "已为以下语言刷新缓存: $1", 65 | "alert-close": "关闭", 66 | "export-failed": "下载失败。", 67 | "exception-url-fetch-error": "无法检索URL:$1", 68 | "exception-rest-page-not-found": "找不到页面“$1”。", 69 | "learn-more": "了解详情", 70 | "error-blocked-1": "您的IP地址目前受到封禁。", 71 | "error-blocked-2": "为防止滥用,WS Export拒绝在维基媒体wiki上被全域封锁的用户访问。", 72 | "error-fix": "如果遇到错误,请尝试以下步骤:", 73 | "error-fix-again": "尝试再次下载电子书。", 74 | "error-fix-epub": "尝试将电子书下载为EPUB文件。", 75 | "error-fix-nocredits": "在“选项”中选中此框以删除署名", 76 | "error-fix-noimages": "在“选项”中选中此框以删除图片。", 77 | "error-breadcrumb": "错误$1", 78 | "error-login": "请登录 WS Export 以继续。", 79 | "error-page-header": "发生错误", 80 | "error-page-details": "错误详情:$1", 81 | "error-page-issue": "如果此错误仍然存在,请$1。", 82 | "error-page-issue-link": "在Phabricator上报告问题", 83 | "onwikiconfig-failure": "无法从以下位置检索 on-wiki 配置: $1", 84 | "exception-fetching-credits": "获取贡献者信息时出错,请再次尝试导出。如果此错误仍存在,请选中“选项”中的框以删除署名。", 85 | "exception-book-conversion": "转换电子书格式时出错。", 86 | "epub-title-page": "封面", 87 | "epub-exported-date": "以$1从维基文库导出", 88 | "epub-about": "关于", 89 | "breadcrumbs-home-link": "首页", 90 | "breadcrumbs-stats-link": "统计", 91 | "recently-popular-heading": "最近流行", 92 | "recently-popular-subtext": "过去三个月最受欢迎的下载", 93 | "change-lang": "更改语言" 94 | } 95 | -------------------------------------------------------------------------------- /i18n/zh-hant.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Kly", 5 | "LNDDYL" 6 | ] 7 | }, 8 | "app-title": "維基文庫電子書匯出", 9 | "app-title-wikisource": "維基文庫", 10 | "app-title-html": "$1電子書匯出", 11 | "app-subtitle": "從維基文庫以多種檔案格式匯出成電子書籍。", 12 | "logo-alt-text": "維基文庫的標誌,為藍白色外觀的冰山圖樣。", 13 | "footer-version": "此為版本 $2 的 $1。", 14 | "version": "版本 $1", 15 | "commit-label": "提交:$1", 16 | "licensed-under": "依據以下$1許可。", 17 | "license-link": "GPL v2 或後續版本", 18 | "source-and-docs": "原始程式碼$1 下可用,以及$2裡的文件。", 19 | "source-link": "在 Github", 20 | "docs-link": "在多語言維基文庫", 21 | "statistics": "一些$1也另外提供。", 22 | "statistics-link": "統計", 23 | "issues": "在 Pabricator 下的 $1 專案回報問題。", 24 | "issue-button": "建立問題", 25 | "back-to-wikisource": "返回到維基文庫", 26 | "previous-alt-text": "方向往後的箭頭圖標,指示該連結會返回到 Wikisource。", 27 | "export-book": "匯出書籍", 28 | "lang-field-label": "維基文庫", 29 | "lang-field-value": "$1($2)", 30 | "lang-field-help": "要匯出成電子書的維基文庫。", 31 | "title-field-label": "標題", 32 | "title-field-help": "要匯出的書籍或段落標題。", 33 | "format-field-label": "檔案格式", 34 | "format-field-help": "", 35 | "format-epub-3": "EPUB 3(適用於多數讀者)", 36 | "format-epub-2": "EPUB 2(已棄用,可適用一些較舊的電子閱讀器)", 37 | "format-htmlz": "HTML", 38 | "format-mobi": "MOBI(適用於 Kindles)", 39 | "format-pdf-a4": "PDF - A4 大小", 40 | "format-pdf-a5": "PDF - A5 大小", 41 | "format-pdf-a6": "PDF - A6 大小", 42 | "format-pdf-letter": "PDF - 美國信封大小", 43 | "format-rtf": "RTF", 44 | "format-txt": "純文字", 45 | "exceeded-rate-limitation": "您在短時間內發出太多請求。請等待 $1 {{PLURAL:$1|分鐘}}後再重新載入此工具。若是登入來避免再次出現這種情況。", 46 | "exception-invalid-format": "「$1」不是有效格式。有效格式為:$2", 47 | "font-field-label": "字型", 48 | "no-font-option": "無(使用設備預設值)", 49 | "font-field-help": "從 $1 種可用文字裡挑選。", 50 | "options-label": "選項", 51 | "credits-field-label": "不提及編輯者名單(下載速度較快)", 52 | "credits-default-message": "[貢獻者清單已依照請求省略掉。]", 53 | "images-field-label": "不包含圖片", 54 | "nocache-field-label": "繞過所有快取(較慢但有助於偵錯)", 55 | "export-button": "匯出", 56 | "cache-updated": "已重新整理 $1 語言的快取", 57 | "alert-close": "關閉", 58 | "export-failed": "下載失敗。", 59 | "exception-url-fetch-error": "無法索取 URL:$1", 60 | "exception-rest-page-not-found": "找不到頁面「$1」。", 61 | "learn-more": "了解更多", 62 | "error-blocked-1": "您的 IP 位址目前已被封鎖。", 63 | "error-blocked-2": "為了防止具破壞性的自動化濫用行為,WS Export 拒絕在 Wikimedia wiki 上已被全域封鎖的註銷使用者存取內容。", 64 | "error-fix": "如果遇到錯誤,請嘗試這些步驟:", 65 | "error-fix-again": "嘗試重新下載電子書。", 66 | "error-fix-epub": "嘗試下載電子書成 EPUB 檔案。", 67 | "error-fix-nocredits": "在「選項」勾選方框來移除名單", 68 | "error-fix-noimages": "在「選項」勾選方框來移除圖片", 69 | "error-breadcrumb": "錯誤 $1", 70 | "error-login": "請登入 WS Export 以繼續。", 71 | "error-page-header": "發生錯誤", 72 | "error-page-details": "錯誤詳細資訊:$1", 73 | "error-page-issue": "如果錯誤持續發生,請$1。", 74 | "error-page-issue-link": "在 Phabricator 上回報問題", 75 | "onwikiconfig-failure": "無法取得來自:$1 的 wiki 上設定", 76 | "exception-fetching-credits": "索取人員名單資訊錯誤。請重試匯出。如果這個錯誤持續發生,請在「選項」勾選移除名單的方框。", 77 | "exception-book-conversion": "轉換電子書格式錯誤。", 78 | "epub-title-page": "標題頁面", 79 | "epub-exported-date": "於 $1 從維基文庫匯出", 80 | "epub-about": "關於", 81 | "breadcrumbs-home-link": "首頁", 82 | "breadcrumbs-stats-link": "統計", 83 | "recently-popular-heading": "近期熱門", 84 | "recently-popular-subtext": "在過去三個月最受歡迎的下載", 85 | "change-lang": "變更語言" 86 | } 87 | -------------------------------------------------------------------------------- /migrations/Version20201216012133.php: -------------------------------------------------------------------------------- 1 | addSql( 'CREATE TABLE IF NOT EXISTS books_generated (time DATETIME NOT NULL, lang VARCHAR(10) NOT NULL, title VARCHAR(255) NOT NULL, format VARCHAR(10) NOT NULL, INDEX time (time), INDEX lang (lang)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB' ); 24 | } 25 | 26 | /** 27 | * Drops the table. 28 | * @param Schema $schema 29 | */ 30 | public function down( Schema $schema ): void { 31 | $this->addSql( 'DROP TABLE books_generated' ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /migrations/Version20201216031438.php: -------------------------------------------------------------------------------- 1 | addSql( 'ALTER TABLE books_generated ADD id INT AUTO_INCREMENT NOT NULL FIRST, ADD PRIMARY KEY (id)' ); 24 | } 25 | 26 | /** 27 | * Remove the id column. 28 | * @param Schema $schema 29 | */ 30 | public function down( Schema $schema ): void { 31 | $this->addSql( 'ALTER TABLE books_generated MODIFY id INT NOT NULL' ); 32 | $this->addSql( 'ALTER TABLE books_generated DROP PRIMARY KEY' ); 33 | $this->addSql( 'ALTER TABLE books_generated DROP id' ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /migrations/Version20201222094802.php: -------------------------------------------------------------------------------- 1 | addSql( 'ALTER TABLE books_generated ADD duration INT DEFAULT NULL' ); 18 | } 19 | 20 | public function down( Schema $schema ): void { 21 | $this->addSql( 'ALTER TABLE books_generated DROP duration' ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "grunt": "1.6.1", 4 | "grunt-banana-checker": "0.13.0", 5 | "grunt-stylelint": "0.20.1", 6 | "stylelint-config-wikimedia": "0.18.0" 7 | }, 8 | "scripts": { 9 | "test": "banana-checker i18n/ && stylelint '**/*.{css,less}'" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | . 17 | ./vendor/ 18 | ./var/ 19 | ./bin/.phpunit/ 20 | ./.phan/ 21 | 22 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | tests 22 | 23 | 24 | 25 | 26 | 27 | src 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/.user.ini: -------------------------------------------------------------------------------- 1 | ; This file contains local php.ini overrides. 2 | ; See https://wikitech.wikimedia.org/wiki/Help:Toolforge/Web/Lighttpd#PHP 3 | ; for documentation. 4 | memory_limit = 256M 5 | -------------------------------------------------------------------------------- /public/img/previous-ltr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | previous 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/img/previous-rtl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | previous 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | bootEnv( dirname( __DIR__ ) . '/.env' ); 11 | 12 | if ( $_SERVER[ 'APP_DEBUG' ] ) { 13 | umask( 0000 ); 14 | 15 | Debug::enable(); 16 | } 17 | 18 | $trustedProxies = $_SERVER[ 'TRUSTED_PROXIES' ] ?? false; 19 | if ( $trustedProxies ) { 20 | Request::setTrustedProxies( 21 | explode( ',', $trustedProxies ), 22 | Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO 23 | ); 24 | } 25 | 26 | $trustedHosts = $_SERVER[ 'TRUSTED_HOSTS' ] ?? false; 27 | if ( $trustedHosts ) { 28 | Request::setTrustedHosts( [ $trustedHosts ] ); 29 | } 30 | 31 | $kernel = new Kernel( $_SERVER[ 'APP_ENV' ], (bool)$_SERVER[ 'APP_DEBUG' ] ); 32 | $request = Request::createFromGlobals(); 33 | $response = $kernel->handle( $request ); 34 | $response->send(); 35 | $kernel->terminate( $request, $response ); 36 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org/ 2 | # https://support.google.com/webmasters/answer/6062608 3 | 4 | User-agent: * 5 | Disallow: / 6 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | header { 2 | background-color: #f5f5f5; 3 | margin-bottom: 25px; 4 | } 5 | 6 | .container { 7 | max-width: 585px; 8 | } 9 | 10 | header .container { 11 | display: flex; 12 | align-items: center; 13 | /* 585 is the container width set above, and the 85 and 20 are for the logo. */ 14 | max-width: calc( 585px + ( ( 85px + 20px ) * 2 ) ); 15 | } 16 | 17 | header .logo { 18 | float: left; 19 | margin: 25px 20px 25px 0; 20 | } 21 | 22 | body.rtl header .logo { 23 | float: right; 24 | margin: 25px 0 25px 20px; 25 | } 26 | 27 | footer { 28 | font-size: smaller; 29 | border-top: 1px solid #e5e5e5; 30 | padding: 25px 0; 31 | margin-top: 2rem; 32 | } 33 | 34 | footer p { 35 | margin: 0; 36 | padding: 0; 37 | } 38 | 39 | footer .change-lang { 40 | margin-top: 10px; 41 | } 42 | 43 | .back-to-wikisource a { 44 | color: inherit; 45 | font-weight: bold; 46 | } 47 | 48 | .back-to-wikisource img { 49 | /* 5px is half of half the line height, in order to center vertically but not around the baseline. */ 50 | vertical-align: -5px; 51 | } 52 | 53 | .back-to-wikisource a:hover { 54 | text-decoration: none; 55 | } 56 | 57 | .back-to-wikisource a:hover span { 58 | text-decoration: underline; 59 | } 60 | 61 | h2 { 62 | border-bottom: 1px solid #e5e5e5; 63 | margin: 25px 0; 64 | font-size: 1.8em; 65 | } 66 | 67 | fieldset legend, 68 | label { 69 | font-size: 1.1em; 70 | font-weight: bold; 71 | border: 0; 72 | margin: 0; 73 | } 74 | 75 | label { 76 | display: block; 77 | margin-bottom: 7px; 78 | } 79 | 80 | fieldset.options p { 81 | margin: 0; 82 | text-indent: -25px; 83 | margin-left: 25px; 84 | } 85 | 86 | fieldset.options input { 87 | width: 25px; 88 | position: relative; 89 | top: 2px; 90 | } 91 | 92 | fieldset.options label { 93 | font-weight: normal; 94 | display: inline; 95 | } 96 | 97 | .submit { 98 | margin: 25px 0 50px 0; 99 | } 100 | 101 | div.error { 102 | display: flex; 103 | } 104 | 105 | div.error > .icon { 106 | flex: 1; 107 | font-size: 2em; 108 | margin-right: 15px; 109 | } 110 | 111 | div.error > .icon .glyphicon { 112 | top: 9px; 113 | } 114 | 115 | div.error > .message { 116 | flex: 20; 117 | padding-top: 0.4em; 118 | } 119 | 120 | div.error p { 121 | margin: 0; 122 | } 123 | 124 | .stats { 125 | padding: 0 1.5rem; 126 | } 127 | 128 | .recents { 129 | background-color: #f5f5f5; 130 | border-radius: 2px; 131 | padding: 1.5rem 2rem; 132 | } 133 | 134 | .recents h1, 135 | .recents h4 { 136 | margin: 0; 137 | } 138 | 139 | .recents h4 { 140 | margin-top: 2px; 141 | } 142 | 143 | .recents ol { 144 | margin-top: 3rem; 145 | padding: 0; 146 | } 147 | 148 | .recents ol li { 149 | list-style: none; 150 | } 151 | 152 | .recent { 153 | margin: 0 auto; 154 | padding-top: 15px; 155 | } 156 | 157 | .recent:not( :last-child ) { 158 | border-bottom: 1px solid #d9d9d9; 159 | padding-bottom: 15px; 160 | } 161 | 162 | .recent div { 163 | padding: 0; 164 | } 165 | 166 | .recent h4, 167 | .recent h3 { 168 | margin: 0; 169 | } 170 | 171 | .recent h3 { 172 | margin-top: 5px; 173 | } 174 | 175 | .recent div:nth-child( 2 ) { 176 | display: flex; 177 | height: 50px; 178 | padding-right: 5px; 179 | align-items: center; 180 | justify-content: flex-end; 181 | } 182 | 183 | .recent img { 184 | height: 25px; 185 | width: 25px; 186 | } 187 | -------------------------------------------------------------------------------- /public/toolinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "wsexport", 3 | "title" : "Wikisource Export", 4 | "description" : "Export Wikisource books to ePub, PDF, and other formats.", 5 | "url" : "https://ws-export.wmcloud.org", 6 | "keywords" : "Wikisource, books, ebooks, ePub, PDF", 7 | "author" : [ 8 | {"name": "Tpt"}, 9 | {"name": "Sam Wilson"}, 10 | {"name": "MusikAnimal"}, 11 | {"name": "Dayllan Maza"}, 12 | {"name": "Jan Berkel"}, 13 | {"name": "phil-el"}, 14 | {"name": "cimurah"}, 15 | {"name": "Max Semenik"}, 16 | {"name": "Daimona"} 17 | ], 18 | "repository" : "https://github.com/wikimedia/ws-export.git", 19 | "available_ui_languages": [ 20 | "ar", 21 | "as", 22 | "ban", 23 | "be-tarask", 24 | "bn", 25 | "ca", 26 | "cs", 27 | "dag", 28 | "da", 29 | "de", 30 | "el", 31 | "en", 32 | "eo", 33 | "es", 34 | "et", 35 | "fa", 36 | "fi", 37 | "fr", 38 | "gl", 39 | "he", 40 | "hu", 41 | "ia", 42 | "id", 43 | "io", 44 | "is", 45 | "it", 46 | "ja", 47 | "kg", 48 | "ko", 49 | "krc", 50 | "ku-latn", 51 | "lb", 52 | "mk", 53 | "ms", 54 | "nb", 55 | "nl", 56 | "no", 57 | "om", 58 | "or", 59 | "pl", 60 | "pt-br", 61 | "pt", 62 | "ru", 63 | "sje", 64 | "sk", 65 | "sl", 66 | "su", 67 | "sv", 68 | "ta", 69 | "te", 70 | "tl", 71 | "tr", 72 | "uk", 73 | "vec", 74 | "vi", 75 | "zh-hans", 76 | "zh-hant" 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /resources/images/Wikisource-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikimedia/ws-export/f86ced7eb1c1b815be5ed1e47b1e2bdc975c2f3c/resources/images/Wikisource-logo.svg.png -------------------------------------------------------------------------------- /resources/styles/mediawiki.css: -------------------------------------------------------------------------------- 1 | body { 2 | line-height: 1.2; 3 | } 4 | 5 | /* images */ 6 | div.floatright { 7 | float: right; 8 | clear: right; 9 | position: relative; 10 | margin: 0.5em 0 0.8em 1.4em; 11 | } 12 | 13 | div.floatleft { 14 | float: left; 15 | clear: left; 16 | position: relative; 17 | margin: 0.5em 1.4em 0.8em 0; 18 | } 19 | 20 | .center { 21 | width: 100%; 22 | text-align: center; 23 | } 24 | 25 | .center * { 26 | margin-left: auto; 27 | margin-right: auto; 28 | } 29 | 30 | .thumbcaption { 31 | text-align: left; 32 | font-size: 95%; 33 | } 34 | 35 | div.tright { 36 | float: right; 37 | clear: right; 38 | margin: 0.5em 0 0.8em 1.4em; 39 | } 40 | 41 | div.tleft { 42 | float: left; 43 | clear: left; 44 | margin: 0.5em 1.4em 0.8em 0; 45 | } 46 | 47 | .small { 48 | font-size: 94%; 49 | } 50 | 51 | /* Styles simplified from resources/src/mediawiki.skinning/content.media-common.less */ 52 | .mw-valign-middle { 53 | vertical-align: middle; 54 | } 55 | 56 | .mw-valign-baseline { 57 | vertical-align: baseline; 58 | } 59 | 60 | .mw-valign-sub { 61 | vertical-align: sub; 62 | } 63 | 64 | .mw-valign-super { 65 | vertical-align: super; 66 | } 67 | 68 | .mw-valign-top { 69 | vertical-align: top; 70 | } 71 | 72 | .mw-valign-text-top { 73 | vertical-align: text-top; 74 | } 75 | 76 | .mw-valign-bottom { 77 | vertical-align: bottom; 78 | } 79 | 80 | .mw-valign-text-bottom { 81 | vertical-align: text-bottom; 82 | } 83 | 84 | .mw-halign-right { 85 | margin: 0 0 0.5em 0.5em; 86 | clear: right; 87 | float: right; 88 | } 89 | 90 | .mw-halign-left { 91 | margin: 0 0.5em 0.5em 0; 92 | clear: left; 93 | float: left; 94 | } 95 | 96 | .mw-halign-none { 97 | clear: none; 98 | float: none; 99 | } 100 | 101 | .mw-halign-center { 102 | margin: 0 auto; 103 | display: table; 104 | border-collapse: collapse; 105 | clear: none; 106 | float: none; 107 | } 108 | -------------------------------------------------------------------------------- /src/Book.php: -------------------------------------------------------------------------------- 1 | array('flags' => array(), 'count' => integer)) 59 | */ 60 | public $credits = []; 61 | } 62 | -------------------------------------------------------------------------------- /src/BookCreator.php: -------------------------------------------------------------------------------- 1 | getGenerator( $format ) 33 | ); 34 | } 35 | 36 | public function __construct( BookProvider $bookProvider, FormatGenerator $bookGenerator ) { 37 | $this->bookProvider = $bookProvider; 38 | $this->bookGenerator = $bookGenerator; 39 | } 40 | 41 | /** 42 | * Create the book. 43 | * @param string $title 44 | * @param string|null $outputPath 45 | */ 46 | public function create( $title, $outputPath = null ): void { 47 | date_default_timezone_set( 'UTC' ); 48 | 49 | $this->book = $this->bookProvider->get( $title ); 50 | $this->filePath = $this->bookGenerator->create( $this->book ); 51 | if ( $outputPath ) { 52 | $this->renameFile( $outputPath ); 53 | } 54 | } 55 | 56 | public function getBook(): Book { 57 | return $this->book; 58 | } 59 | 60 | public function getMimeType() { 61 | return $this->bookGenerator->getMimeType(); 62 | } 63 | 64 | public function getExtension() { 65 | return $this->bookGenerator->getExtension(); 66 | } 67 | 68 | public function getFilePath(): string { 69 | return $this->filePath; 70 | } 71 | 72 | /** 73 | * Get a sanitized filename for the created book. 74 | * @return string 75 | */ 76 | public function getFilename(): string { 77 | return str_replace( [ '/', '\\' ], '_', trim( $this->book->title ) ) . '.' . $this->getExtension(); 78 | } 79 | 80 | /** 81 | * Move the created book to a new directory. 82 | * @param string $dest The destination directory. 83 | * @throws Exception If the file could not be renamed. 84 | */ 85 | private function renameFile( string $dest ): void { 86 | if ( !is_dir( $dest ) ) { 87 | throw new Exception( 'Not a directory: ' . $dest ); 88 | } 89 | $newFilePath = $dest . '/' . $this->getFilename(); 90 | if ( !is_dir( dirname( $newFilePath ) ) ) { 91 | mkdir( dirname( $newFilePath ), 0755, true ); 92 | } 93 | if ( !rename( $this->getFilePath(), $newFilePath ) ) { 94 | throw new Exception( 'Unable to create output file: ' . $newFilePath ); 95 | } 96 | $this->filePath = $newFilePath; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Command/ExportCommand.php: -------------------------------------------------------------------------------- 1 | generatorSelector = $generatorSelector; 36 | $this->creditRepo = $creditRepo; 37 | $this->api = $api; 38 | $this->enableCache = $enableCache; 39 | $this->fileCache = $fileCache; 40 | } 41 | 42 | protected function configure(): void { 43 | $formatDesc = 'Export format. One of: ' . implode( ', ', array_keys( GeneratorSelector::getValidFormats() ) ); 44 | $this->setDescription( 'Export a book.' ) 45 | ->addOption( 'lang', 'l', InputOption::VALUE_REQUIRED, 'Wikisource language code.' ) 46 | ->addOption( 'title', 't', InputOption::VALUE_REQUIRED, 'Wiki page name of the work to export. Required' ) 47 | ->addOption( 'format', 'f', InputOption::VALUE_REQUIRED, $formatDesc, 'epub-3' ) 48 | ->addOption( 'path', 'p', InputOption::VALUE_REQUIRED, 'Filesystem path to export to.', dirname( __DIR__, 2 ) ) 49 | ->addOption( 'nocache', null, InputOption::VALUE_NONE, 'Do not cache anything (re-fetch all data).' ) 50 | ->addOption( 'nocredits', null, InputOption::VALUE_NONE, 'Do not include the credits list in the exported ebook.' ); 51 | } 52 | 53 | protected function execute( InputInterface $input, OutputInterface $output ): int { 54 | $timeStart = microtime( true ); 55 | $io = new SymfonyStyle( $input, $output ); 56 | 57 | if ( !$input->getOption( 'title' ) ) { 58 | $io->warning( 'Please provide a title with the --title option.' ); 59 | return Command::FAILURE; 60 | } 61 | 62 | if ( !$input->getOption( 'lang' ) ) { 63 | $io->warning( 'Please provide a language code with the --lang option.' ); 64 | return Command::FAILURE; 65 | } 66 | 67 | $format = $input->getOption( 'format' ); 68 | if ( !in_array( $format, GeneratorSelector::getAllFormats() ) ) { 69 | $msgFormat = '"%s" is not a valid format. Valid formats are: %s'; 70 | $msg = sprintf( $msgFormat, $format, '"' . implode( '", "', array_keys( GeneratorSelector::getValidFormats() ) ) . '"' ); 71 | $io->warning( $msg ); 72 | return Command::FAILURE; 73 | } 74 | 75 | $input->validate(); 76 | $options = [ 77 | 'images' => true, 78 | 'credits' => !$input->getOption( 'nocredits' ), 79 | ]; 80 | $this->api->setLang( $input->getOption( 'lang' ) ); 81 | if ( $input->getOption( 'nocache' ) || !$this->enableCache ) { 82 | $io->writeln( 'Caching is disabled.' ); 83 | $this->api->disableCache(); 84 | } 85 | $creator = BookCreator::forApi( $this->api, $input->getOption( 'format' ), $options, $this->generatorSelector, $this->creditRepo, $this->fileCache ); 86 | $creator->create( $input->getOption( 'title' ), $input->getOption( 'path' ) ); 87 | 88 | $io->success( [ 89 | 'The ebook has been created: ' . $creator->getFilePath(), 90 | 'Memory usage: ' . ( memory_get_peak_usage( true ) / 1024 / 1024 ) . ' MiB', 91 | 'Total time: ' . round( microtime( true ) - $timeStart, 1 ) . ' seconds', 92 | ] ); 93 | 94 | return Command::SUCCESS; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Command/OpdsCommand.php: -------------------------------------------------------------------------------- 1 | api = $api; 30 | $this->creditRepo = $creditRepo; 31 | $this->fileCache = $fileCache; 32 | } 33 | 34 | protected function configure(): void { 35 | $this->setDescription( 'Generate an OPDS file.' ) 36 | ->addOption( 'lang', 'l', InputOption::VALUE_REQUIRED, 'Wikisource language code.' ) 37 | ->addOption( 'category', 'c', InputOption::VALUE_REQUIRED, 'Category name to export.' ); 38 | } 39 | 40 | protected function execute( InputInterface $input, OutputInterface $output ): int { 41 | $io = new SymfonyStyle( $input, $output ); 42 | 43 | if ( !$input->getOption( 'lang' ) ) { 44 | $io->warning( 'Please provide a language code with the --lang option.' ); 45 | return Command::FAILURE; 46 | } 47 | 48 | if ( !$input->getOption( 'category' ) ) { 49 | $io->warning( 'Please provide a category with the --category option.' ); 50 | return Command::FAILURE; 51 | } 52 | 53 | $lang = $input->getOption( 'lang' ); 54 | $category = str_replace( ' ', '_', $input->getOption( 'category' ) ); 55 | 56 | date_default_timezone_set( 'UTC' ); 57 | $this->api->setLang( $lang ); 58 | $provider = new BookProvider( $this->api, [ 'categories' => false, 'images' => false ], $this->creditRepo, $this->fileCache ); 59 | 60 | $exportPath = 'https://ws-export.wmcloud.org/'; 61 | $atomGenerator = new OpdsBuilder( $provider, $this->api, $lang, $this->fileCache, $exportPath ); 62 | $outputFile = dirname( __DIR__, 2 ) . "/public/opds/$lang/$category.xml"; 63 | if ( !is_dir( dirname( $outputFile ) ) ) { 64 | mkdir( dirname( $outputFile ), 0755, true ); 65 | } 66 | file_put_contents( $outputFile, $atomGenerator->buildFromCategory( 'Category:' . $category ) ); 67 | 68 | $io->success( "The OPDS file has been created: $outputFile" ); 69 | return Command::SUCCESS; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Controller/StatisticsController.php: -------------------------------------------------------------------------------- 1 | query->get( 'month', $now->format( 'm' ) ); 29 | $year = $request->query->get( 'year', $now->format( 'Y' ) ); 30 | 31 | $stat = $generatedBookRepo->getTypeAndLangStats( $month, $year ); 32 | 33 | $val = []; 34 | $total = []; 35 | foreach ( $stat as $format => $temp ) { 36 | $format = $this->normalizeFormat( $format ); 37 | foreach ( $temp as $lang => $num ) { 38 | if ( $lang === '' ) { 39 | $lang = 'oldwiki'; 40 | } 41 | if ( !array_key_exists( $lang, $val ) ) { 42 | $val[ $lang ] = []; 43 | } 44 | if ( !array_key_exists( $format, $val[ $lang ] ) ) { 45 | $val[ $lang ][ $format ] = 0; 46 | } 47 | $val[ $lang ][ $format ] += $num; 48 | 49 | if ( !array_key_exists( $format, $total ) ) { 50 | $total[ $format ] = 0; 51 | } 52 | $total[ $format ] += $num; 53 | } 54 | } 55 | 56 | ksort( $val ); 57 | ksort( $total ); 58 | 59 | return $this->render( 'statistics.html.twig', [ 60 | 'recently_popular' => $generatedBookRepo->getRecentPopular(), 61 | 'month' => $month, 62 | 'year' => $year, 63 | 'val' => $val, 64 | 'total' => $total, 65 | ] ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Entity/GeneratedBook.php: -------------------------------------------------------------------------------- 1 | setTime( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) ); 67 | $this->setLang( $book->lang ); 68 | $this->setTitle( $book->title ); 69 | $this->setFormat( $format ); 70 | if ( $duration ) { 71 | // Round up to the nearest second. 72 | $this->setDuration( ceil( $duration->getDuration() / 1000 ) ); 73 | } 74 | } 75 | 76 | /** 77 | * @return int|null 78 | */ 79 | public function getId(): ?int { 80 | return $this->id; 81 | } 82 | 83 | /** 84 | * @return DateTime 85 | */ 86 | public function getTime(): DateTime { 87 | return $this->time; 88 | } 89 | 90 | /** 91 | * @param DateTime $time 92 | */ 93 | public function setTime( DateTime $time ): void { 94 | $this->time = $time; 95 | } 96 | 97 | /** 98 | * @return string 99 | */ 100 | public function getLang(): string { 101 | return $this->lang; 102 | } 103 | 104 | /** 105 | * @param string $lang 106 | */ 107 | public function setLang( string $lang ): void { 108 | $this->lang = $lang; 109 | } 110 | 111 | /** 112 | * @return string 113 | */ 114 | public function getTitle(): string { 115 | return $this->title; 116 | } 117 | 118 | /** 119 | * @param string $title 120 | */ 121 | public function setTitle( string $title ): void { 122 | $this->title = htmlspecialchars_decode( $title ); 123 | } 124 | 125 | /** 126 | * @return string 127 | */ 128 | public function getFormat(): string { 129 | return $this->format; 130 | } 131 | 132 | /** 133 | * @param string $format 134 | */ 135 | public function setFormat( string $format ): void { 136 | $this->format = $format; 137 | } 138 | 139 | /** 140 | * Get the generation duration in seconds. 141 | * @return int|null 142 | */ 143 | public function getDuration(): ?int { 144 | return $this->duration; 145 | } 146 | 147 | /** 148 | * @param int|null $duration The duration in seconds. 149 | */ 150 | public function setDuration( ?int $duration ): void { 151 | $this->duration = $duration; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/EpubCheck/EpubCheck.php: -------------------------------------------------------------------------------- 1 | epubCheckPath = $epubCheckPath; 16 | } 17 | 18 | /** 19 | * @param string $filePath 20 | * @return Result[] 21 | */ 22 | public function check( string $filePath ): array { 23 | if ( !file_exists( $this->epubCheckPath ) ) { 24 | throw new Exception( 'epubcheck not found at ' . $this->epubCheckPath ); 25 | } 26 | $jsonFile = tempnam( sys_get_temp_dir(), 'results-' . $filePath . '.json' ); 27 | $process = new Process( [ 'java', '-jar', $this->epubCheckPath, '--quiet', '--json', $jsonFile, $filePath ] ); 28 | $process->run(); 29 | $decoded = json_decode( file_get_contents( $jsonFile ), true ); 30 | unlink( $jsonFile ); 31 | $results = []; 32 | foreach ( $decoded['messages'] ?? [] as $message ) { 33 | $locations = $this->mapLocations( $filePath, $message['locations'] ); 34 | $results[] = new Result( $message['severity'], $message['message'], $locations, $message['additionalLocations'] ); 35 | } 36 | return $results; 37 | } 38 | 39 | /** 40 | * @return Location[] 41 | */ 42 | private function mapLocations( string $epubPath, array $data ): array { 43 | $locations = []; 44 | foreach ( $data as $i => $location ) { 45 | $path = $location['path']; 46 | $line = $location['line']; 47 | $column = $location['column']; 48 | if ( $line === -1 ) { 49 | continue; 50 | } 51 | if ( $i === 0 ) { 52 | // Retrieve context source for the first location only, for quicker tests. 53 | $zip = new ZipArchive(); 54 | $zip->open( $epubPath ); 55 | $fileContents = $zip->getFromName( $path ); 56 | $lines = explode( "\n", $fileContents ); 57 | $contextLines = array_slice( $lines, $line - 2, 3, true ); 58 | } else { 59 | $contextLines = []; 60 | } 61 | $locations[] = new Location( $path, $line, $column, $contextLines ); 62 | } 63 | return $locations; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/EpubCheck/Location.php: -------------------------------------------------------------------------------- 1 | path = $path; 15 | $this->line = $line; 16 | $this->column = $column; 17 | $this->contextLines = $contextLines; 18 | } 19 | 20 | public function __toString(): string { 21 | return "/$this->path:$this->line:$this->column\n" . implode( "\n", $this->contextLines ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/EpubCheck/Result.php: -------------------------------------------------------------------------------- 1 | message = $message; 20 | $this->severity = $severity; 21 | $this->locations = $locations; 22 | $this->additionalLocations = $additionalLocations; 23 | } 24 | 25 | public function isError(): bool { 26 | return $this->severity === 'ERROR'; 27 | } 28 | 29 | public function isWarning(): bool { 30 | return $this->severity === 'WARNING'; 31 | } 32 | 33 | public function getMessage(): string { 34 | return $this->message; 35 | } 36 | 37 | public function getLocations(): array { 38 | return $this->locations; 39 | } 40 | 41 | public function toString(): string { 42 | $allLocations = "\n\n\t" . implode( "\n\t", array_map( static function ( Location $l ) { 43 | return $l->__toString(); 44 | }, $this->locations ) ); 45 | if ( $this->additionalLocations > 0 ) { 46 | $allLocations .= "\n\t + " . $this->additionalLocations . ' other locations'; 47 | } 48 | return $this->message . $allLocations; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Exception/WsExportException.php: -------------------------------------------------------------------------------- 1 | */ 13 | private $i18nParams; 14 | 15 | /** @var int */ 16 | private $responseCode; 17 | 18 | /** @var bool */ 19 | private $friendly; 20 | 21 | /** 22 | * WsExportException constructor. 23 | * @param string $i18nMessage 24 | * @param array $i18nParams 25 | * @param int $responseCode 26 | * @param bool $friendly `true` will mean a 'Learn more' link will be shown with troubleshooting tips. 27 | */ 28 | public function __construct( string $i18nMessage, array $i18nParams, int $responseCode, bool $friendly = true ) { 29 | parent::__construct(); 30 | $this->i18nMessage = $i18nMessage; 31 | $this->i18nParams = $i18nParams; 32 | $this->responseCode = $responseCode; 33 | $this->friendly = $friendly; 34 | } 35 | 36 | public function getI18nMessage(): string { 37 | return 'exception-' . $this->i18nMessage; 38 | } 39 | 40 | public function getI18nParams(): array { 41 | return $this->i18nParams; 42 | } 43 | 44 | public function getResponseCode(): int { 45 | return $this->responseCode; 46 | } 47 | 48 | /** 49 | * Is this a 'friendly' error message ('Learn more' tips should be shown)? 50 | * @return bool 51 | */ 52 | public function isFriendly(): bool { 53 | return $this->friendly; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/FileCache.php: -------------------------------------------------------------------------------- 1 | dir = $this->makePrivateDirectory( $path ); 31 | 32 | // Randomly clean up the cache 33 | if ( mt_rand( 0, 100 ) <= self::CLEANUP_RATE ) { 34 | register_shutdown_function( function () { 35 | $this->cleanup(); 36 | } ); 37 | } 38 | } 39 | 40 | /** 41 | * Builds a unique temporary file name for a given title and extension. 42 | * 43 | * @param string $title 44 | * @param string $extension 45 | * @return string 46 | */ 47 | public function buildTemporaryFileName( $title, $extension ) { 48 | for ( $i = 0; $i < 100; $i++ ) { 49 | $path = $this->getDirectory() . '/' . 'ws-' . Util::encodeString( $title ) . '-' . getmypid() . rand() . '.' . $extension; 50 | if ( !file_exists( $path ) ) { 51 | return $path; 52 | } 53 | } 54 | 55 | throw new Exception( 'Unable to create temporary file' ); 56 | } 57 | 58 | /** 59 | * @return string Directory for temporary files 60 | */ 61 | public function getDirectory(): string { 62 | return $this->dir; 63 | } 64 | 65 | /** 66 | * Creates a tool-specific temporary directory with specific permissions 67 | * 68 | * @param string $path 69 | * @return string 70 | */ 71 | private function makePrivateDirectory( string $path ): string { 72 | // Use username in the path. The tool needs its own directory so that it knows which 73 | // files to garbage collect. Can't just use the hardcoded name because 74 | // wsexport and wsexport-test might end up on the same node, stepping on each other's toes. 75 | $user = get_current_user(); 76 | // Remove leading "tools." present if running on labs 77 | $user = preg_replace( '/^tools\./', '', $user ); 78 | $dir = rtrim( $path, '/' ) . '/' . $user; 79 | 80 | // Guard against realpath() returning false sometimes 81 | $dir = realpath( $dir ) ?: $dir; 82 | 83 | if ( !is_dir( $dir ) ) { 84 | if ( !mkdir( $dir, 0755, true ) ) { 85 | throw new Exception( "Couldn't create temporary directory $dir" ); 86 | } 87 | } 88 | 89 | return $dir; 90 | } 91 | 92 | /** 93 | * Cleans up file cache, deleting old files 94 | */ 95 | protected function cleanup(): void { 96 | $di = new DirectoryIterator( $this->dir ); 97 | foreach ( $di as $file ) { 98 | if ( $file->isFile() && !$file->isDot() ) { 99 | $this->checkFile( $file->getPathname() ); 100 | } 101 | } 102 | } 103 | 104 | /** 105 | * Checks whether the given file is present locally and not stale. 106 | * Deletes the file if it's stale. 107 | * @param string $fileName 108 | */ 109 | protected function checkFile( string $fileName ): void { 110 | // phpcs:disable Generic.PHP.NoSilencedErrors.Discouraged 111 | $stat = @stat( $fileName ); 112 | // phpcs:enable 113 | // Failed stat means something went wrong, directories are used for per-wiki metadata 114 | if ( !$stat || is_dir( $fileName ) || $fileName === '.gitkeep' ) { 115 | return; 116 | } 117 | if ( $stat['mtime'] + self::CACHE_DURATION < time() ) { 118 | unlink( $fileName ); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Generator/FormatGenerator.php: -------------------------------------------------------------------------------- 1 | 'epub-3', 33 | // Returning RTF for ODT is a hack in order to not break existing URLs. 34 | 'odt' => 'rtf', 35 | // A5 was more popular than A4 in 2020. T269726. 36 | 'pdf' => 'pdf-a5', 37 | ]; 38 | 39 | /** @var FontProvider */ 40 | private $fontProvider; 41 | 42 | /** @var Api */ 43 | private $api; 44 | 45 | /** @var ConvertGenerator */ 46 | private $convertGenerator; 47 | 48 | /** @var Intuition */ 49 | private $intuition; 50 | 51 | /** @var CacheInterface */ 52 | private $cache; 53 | 54 | /** @var FileCache */ 55 | private $fileCache; 56 | 57 | public function __construct( FontProvider $fontProvider, Api $api, ConvertGenerator $convertGenerator, Intuition $intuition, CacheInterface $cache, FileCache $fileCache ) { 58 | $this->fontProvider = $fontProvider; 59 | $this->api = $api; 60 | $this->convertGenerator = $convertGenerator; 61 | $this->intuition = $intuition; 62 | $this->cache = $cache; 63 | $this->fileCache = $fileCache; 64 | } 65 | 66 | /** 67 | * @return string[] All format names (including aliases). 68 | */ 69 | public static function getAllFormats(): array { 70 | return array_merge( self::getValidFormats(), array_keys( self::$aliases ) ); 71 | } 72 | 73 | /** 74 | * @return string[] Valid format names (excluding aliases). 75 | */ 76 | public static function getValidFormats(): array { 77 | return self::$formats; 78 | } 79 | 80 | public function getGenerator( $format ): FormatGenerator { 81 | // Resolve alias. 82 | if ( array_key_exists( $format, self::$aliases ) ) { 83 | $format = self::$aliases[$format]; 84 | } 85 | if ( $format === 'epub-3' ) { 86 | return new EpubGenerator( $this->fontProvider, $this->api, $this->intuition, $this->cache, $this->fileCache ); 87 | } elseif ( in_array( $format, ConvertGenerator::getSupportedTypes() ) ) { 88 | $this->convertGenerator->setFormat( $format ); 89 | return $this->convertGenerator; 90 | } elseif ( $format === 'atom' ) { 91 | return new AtomGenerator( $this->fileCache ); 92 | } else { 93 | throw new Exception( "The file format '$format' is unknown." ); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | import( '../config/{packages}/*.yaml' ); 15 | $container->import( '../config/{packages}/' . $this->environment . '/*.yaml' ); 16 | $container->import( '../config/{services}.yaml' ); 17 | $container->import( '../config/{services}_' . $this->environment . '.yaml' ); 18 | } 19 | 20 | protected function configureRoutes( RoutingConfigurator $routes ): void { 21 | $routes->import( '../config/{routes}/' . $this->environment . '/*.yaml' ); 22 | $routes->import( '../config/{routes}/*.yaml' ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/OpdsBuilder.php: -------------------------------------------------------------------------------- 1 | bookProvider = $bookProvider; 51 | $this->api = $api; 52 | $this->lang = $lang; 53 | $this->fileCache = $fileCache; 54 | $this->exportBasePath = $exportBasePath; 55 | } 56 | 57 | /** 58 | * @param string $categoryTitle 59 | * @return bool|string 60 | * @throws Exception 61 | */ 62 | public function buildFromCategory( $categoryTitle ) { 63 | $response = $this->api->completeQuery( [ 64 | 'generator' => 'categorymembers', 65 | 'gcmtitle' => $categoryTitle, 66 | 'gcmnamespace' => '0', 67 | 'prop' => 'info', 68 | 'gcmlimit' => '100', 69 | ] ); 70 | if ( !array_key_exists( 'query', $response ) ) { 71 | throw new Exception( "Category not found: $categoryTitle" ); 72 | } 73 | 74 | $pages = $response['query']['pages']; 75 | 76 | $titles = []; 77 | foreach ( $pages as $page ) { 78 | $titles[] = $page['title']; 79 | } 80 | 81 | return $this->buildFromTitles( $titles, $categoryTitle ); 82 | } 83 | 84 | private function buildFromTitles( array $titles, $fromPage = '' ) { 85 | $generator = new AtomGenerator( $this->fileCache ); 86 | $generator->setExportBasePath( $this->exportBasePath ); 87 | 88 | $dom = new DOMDocument( '1.0', 'UTF-8' ); 89 | $feed = $dom->createElement( 'feed' ); 90 | $generator->appendNamespaces( $feed ); 91 | $feed->setAttribute( 'xml:lang', $this->lang ); 92 | $this->addNode( $dom, $feed, 'title', $fromPage ); 93 | $this->addNode( $dom, $feed, 'updated', date( DATE_ATOM ) ); 94 | if ( $fromPage !== '' ) { 95 | $wsUrl = Util::wikisourceUrl( $this->lang, $fromPage ); 96 | $this->addNode( $dom, $feed, 'id', $wsUrl, 'dcterms:URI' ); 97 | $this->addLink( $dom, $feed, 'alternate', $wsUrl, 'text/html' ); 98 | } 99 | 100 | foreach ( array_chunk( $titles, 5 ) as $chunk ) { 101 | foreach ( $this->bookProvider->getMulti( $chunk, true ) as $book ) { 102 | $entry = $generator->buildEntry( $book, $dom ); 103 | $feed->appendChild( $entry ); 104 | } 105 | } 106 | 107 | $dom->appendChild( $feed ); 108 | 109 | return $dom->saveXML(); 110 | } 111 | 112 | private function addNode( DOMDocument $dom, DOMElement $head, $name, $value, $type = '' ) { 113 | if ( $value === '' ) { 114 | return; 115 | } 116 | 117 | $node = $dom->createElement( $name, $value ); 118 | 119 | if ( $type !== '' ) { 120 | $node->setAttribute( 'xsi:type', $type ); 121 | } 122 | 123 | $head->appendChild( $node ); 124 | } 125 | 126 | private function addLink( DOMDocument $dom, DOMElement $head, $rel, $href, $type = '' ) { 127 | $node = $dom->createElement( 'link' ); 128 | $node->setAttribute( 'rel', $rel ); 129 | if ( $type !== '' ) { 130 | $node->setAttribute( 'type', $type ); 131 | } 132 | $node->setAttribute( 'href', $href ); 133 | $head->appendChild( $node ); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Page.php: -------------------------------------------------------------------------------- 1 | title = $title; 33 | $page->name = $name; 34 | return $page; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Picture.php: -------------------------------------------------------------------------------- 1 | addFile( $this->tempFile, $localName ); 58 | } 59 | 60 | /** 61 | * makes sure that temporary file gets deleted 62 | */ 63 | public function __destruct() { 64 | if ( $this->tempFile && file_exists( $this->tempFile ) ) { 65 | unlink( $this->tempFile ); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Refresh.php: -------------------------------------------------------------------------------- 1 | api = $api; 18 | $this->cacheItemPool = $cacheItemPool; 19 | } 20 | 21 | public function refresh() { 22 | $this->cacheItemPool->deleteItems( [ 23 | 'namespaces_' . $this->api->getLang(), 24 | 'about_' . $this->api->getLang(), 25 | 'css_' . $this->api->getLang(), 26 | ] ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Repository/GeneratedBookRepository.php: -------------------------------------------------------------------------------- 1 | DATE_SUB(CURRENT_DATE, INTERVAL 3 month)' 26 | . ' GROUP BY `lang`, `title`' 27 | . ' ORDER BY `total` DESC' 28 | . ' LIMIT 20'; 29 | 30 | return $this->getEntityManager() 31 | ->getConnection() 32 | ->executeQuery( $sql ) 33 | ->fetchAllAssociative(); 34 | } 35 | 36 | /** 37 | * Get total counts of exports by type and language. 38 | * @param string $month The month number. 39 | * @param string $year The year. 40 | * @return int[][] Total counts, keyed by format and then language code. 41 | */ 42 | public function getTypeAndLangStats( string $month, string $year ): array { 43 | if ( !is_numeric( $month ) ) { 44 | $month = null; 45 | } 46 | if ( !is_numeric( $year ) ) { 47 | $year = null; 48 | } 49 | $sql = 'SELECT `format`, `lang`, COUNT(1) AS `number` ' 50 | . ' FROM books_generated' 51 | . ' WHERE YEAR(`time`) = :year AND MONTH(`time`) = :month' 52 | . ' GROUP BY `format`, `lang`'; 53 | $cursor = $this->getEntityManager() 54 | ->getConnection() 55 | ->executeQuery( $sql, [ 56 | 'month' => $month, 57 | 'year' => $year, 58 | ] ) 59 | ->fetchAllAssociative(); 60 | 61 | $stats = []; 62 | foreach ( $cursor as $row ) { 63 | $stats[$row['format']][$row['lang']] = (int)$row['number']; 64 | } 65 | 66 | return $stats; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Util/LoggingMiddleware.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 22 | $this->nextHandler = $nextHandler; 23 | } 24 | 25 | public function __invoke( RequestInterface $request, array $options ) { 26 | $this->logger->debug( $request->getMethod() . ' ' . $request->getUri() ); 27 | $fn = $this->nextHandler; 28 | return $fn( $request, $options )->then( function ( ResponseInterface $response ) { 29 | if ( $response->getStatusCode() < 200 || $response->getStatusCode() > 299 ) { 30 | $this->logger->warning( 'HTTP response ' . $response->getStatusCode() ); 31 | } 32 | return $response; 33 | } ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Util/OnWikiConfig.php: -------------------------------------------------------------------------------- 1 | api = $api; 28 | $this->cache = $cache; 29 | $this->intuition = $intuition; 30 | } 31 | 32 | /** 33 | * Get the on-wiki configuration from the `MediaWiki:WS_Export.json` page. Cached for 1 month. 34 | * @return mixed[] With keys: defaultFont 35 | */ 36 | private function getData( string $lang ): array { 37 | if ( isset( $this->data[ $lang ] ) ) { 38 | return $this->data[ $lang ]; 39 | } 40 | $this->api->setLang( $lang ); 41 | $cacheKey = Util::sanitizeCacheKey( 'OnWikiConfig_' . $lang ); 42 | $this->data[ $lang ] = $this->cache->get( $cacheKey, function ( CacheItemInterface $cacheItem ) { 43 | $cacheItem->expiresAfter( new DateInterval( 'P1M' ) ); 44 | $configPageName = 'MediaWiki:WS_Export.json'; 45 | try { 46 | $dataUrl = 'https://' . $this->api->getDomainName() . '/w/index.php?title=' . $configPageName . '&action=raw&ctype=application/json'; 47 | $json = $this->api->get( $dataUrl ); 48 | } catch ( ConnectException $exception ) { 49 | // If an invalid wiki language, or connection fails for any reason. 50 | $json = '{}'; 51 | } 52 | $data = json_decode( $json, true ); 53 | if ( $data === null ) { 54 | $data = []; 55 | } 56 | if ( !$data ) { 57 | // Don't cache the empty result. 58 | $cacheItem->expiresAt( new DateTime() ); 59 | } 60 | return $data; 61 | } ); 62 | return $this->data[ $lang ]; 63 | } 64 | 65 | /** 66 | * Get the name of the default font to embed in exports. 67 | * 68 | * @param string $lang 69 | * @return string 70 | */ 71 | public function getDefaultFont( string $lang ): string { 72 | $config = $this->getData( $lang ); 73 | return $config['defaultFont'] ?? ''; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Util/Semaphore/Semaphore.php: -------------------------------------------------------------------------------- 1 | semaphoreKey = $semaphoreKey; 26 | $this->capacity = $capacity; 27 | $this->semaphore = null; 28 | } 29 | 30 | public function tryLock(): ?SemaphoreHandle { 31 | if ( $this->semaphore === null ) { 32 | $semaphore = sem_get( $this->semaphoreKey, $this->capacity ); 33 | if ( $semaphore === false ) { 34 | throw new Exception( "Failed to create semaphore key $this->semaphoreKey" ); 35 | } 36 | $this->semaphore = $semaphore; 37 | } 38 | 39 | if ( !sem_acquire( $this->semaphore, true ) ) { 40 | return null; // Semaphore already full 41 | } 42 | 43 | // We return the handle 44 | return new UnixSemaphoreHandle( $this->semaphore ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Util/Semaphore/UnixSemaphoreHandle.php: -------------------------------------------------------------------------------- 1 | semaphore = $semaphore; 15 | $this->isReleased = false; 16 | } 17 | 18 | public function release(): void { 19 | if ( $this->isReleased ) { 20 | return; // Already released 21 | } 22 | $this->isReleased = true; 23 | sem_release( $this->semaphore ); 24 | } 25 | 26 | public function __destruct() { 27 | $this->release(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /templates/_month_year_selector.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
Month
6 | 7 |
8 | 9 |
10 |
Year
11 | 12 |
13 |
14 | 15 |
-------------------------------------------------------------------------------- /templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ msg('app-title') }} 6 | 7 | {% if is_rtl() %} 8 | 9 | {% endif %} 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 20 |
21 |

22 | {% set app_title_strong %} 23 | {{ msg('app-title-wikisource') }} 24 | {% endset %} 25 | {{ msg( 'app-title-html', [app_title_strong~''] ) }} 26 |

27 |

{{ msg( 'app-subtitle' ) }}

28 |
29 |
30 |
31 | 32 |
33 |
34 | {% block main %} 35 | {% endblock %} 36 | 37 | 96 |
97 |
98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /templates/bundles/TwigBundle/Exception/error.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block main %} 4 | 5 | 9 | 10 |
11 |
12 | 13 |
14 |
15 |

16 | {{ msg('error-page-header') }} 17 |

18 |

19 | {{ msg('error-page-details', [exception.message]) }} 20 |

21 | {% if exception.statusCode == 403 %} 22 |

{{ msg('error-blocked-1') }}

23 |

{{ msg('error-blocked-2') }}

24 | {% else %} 25 |

26 | {% set issue_link %} 27 | 28 | {{ msg( 'error-page-issue-link' ) -}} 29 | 30 | {%- endset %} 31 | {{ msg( 'error-page-issue', [ issue_link ] ) }} 32 |

33 | {% endif %} 34 | {% if exception.statusCode == 403 or exception.statusCode == 429 %} 35 |

{{ msg('error-login') }}

36 | {% endif %} 37 |
38 |
39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /templates/statistics.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block main %} 4 | 5 |
6 | 10 |
11 |
12 |

{{ msg('recently-popular-heading') }}

13 |

{{ msg('recently-popular-subtext') }}

14 |
    15 | {% for book in recently_popular %} 16 |
  1. 17 |
    18 |

    19 | {{ book.total }} 20 | {% if book.total > 1 %} 21 | downloads 22 | {% else %} 23 | download 24 | {% endif %} 25 |

    26 |

    27 | 28 | {{ book.title|replace({'_': ' '}) }} 29 | 30 |

    31 |
    32 |
    33 | 34 | The epub logo. 35 | 36 |
    37 |
  2. 38 | {% endfor %} 39 |
40 |
41 |
42 | {% include '_month_year_selector.html.twig' with {month: month, year: year} %} 43 | 44 | 45 | 46 | 47 | 48 | {% for format,value in total %} 49 | 50 | {% endfor %} 51 | 52 | 53 | 54 | {% for lang,temp in val %} 55 | 56 | 57 | {% for format,value in total %} 58 | 65 | {% endfor %} 66 | 67 | {% endfor %} 68 | 69 | 70 | 71 | 72 | {% for format,value in total %} 73 | 74 | {% endfor %} 75 | 76 | 77 |
Stats for {{ month }}/{{ year }}
Language{{ format }}
{{ lang }} 59 | {% if attribute(temp, format) is defined %} 60 | {{ attribute(temp, format) }} 61 | {% else %} 62 | 0 63 | {% endif %} 64 |
Total{{ value }}
78 | {% include '_month_year_selector.html.twig' with {month: month, year: year} %} 79 |
80 |
81 |
82 | 83 | 84 | {% endblock %} 85 | -------------------------------------------------------------------------------- /tests/Book/RefreshTest.php: -------------------------------------------------------------------------------- 1 | setLang( 'en' ); 28 | $refresh = new Refresh( $api, $cache ); 29 | $intuition = new Intuition(); 30 | 31 | // Test that the cache is initially empty. 32 | $this->assertFalse( $cache->hasItem( 'css_en' ) ); 33 | 34 | // Export a book, and test that this fills the cache. 35 | $fileCache = self::getContainer()->get( FileCache::class ); 36 | $epubGenerator = new EpubGenerator( new FontProvider( $cache, new OnWikiConfig( $api, $cache, $intuition ) ), $api, $intuition, $cache, $fileCache ); 37 | $book = new Book(); 38 | $book->lang = 'en'; 39 | $book->title = 'Emma'; 40 | $book->options = [ 'images' => false, 'fonts' => null, 'credits' => false ]; 41 | $epubGenerator->create( $book ); 42 | $this->assertTrue( $cache->hasItem( 'css_en' ) ); 43 | 44 | // Then refresh, and check again that the cache is empty. 45 | $refresh->refresh(); 46 | $this->assertFalse( $cache->hasItem( 'css_en' ) ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/BookCreator/BookCreatorIntegrationTest.php: -------------------------------------------------------------------------------- 1 | fontProvider = new FontProvider( $cache, self::getContainer()->get( OnWikiConfig::class ) ); 50 | $this->api = self::getContainer()->get( Api::class ); 51 | $this->intuition = self::getContainer()->get( Intuition::class ); 52 | $this->fileCache = self::getContainer()->get( FileCache::class ); 53 | $epubGenerator = new EpubGenerator( $this->fontProvider, $this->api, $this->intuition, $cache, $this->fileCache ); 54 | $convertGenerator = new ConvertGenerator( 30, $this->fileCache, $epubGenerator ); 55 | $this->generatorSelector = new GeneratorSelector( $this->fontProvider, $this->api, $convertGenerator, $this->intuition, $cache, $this->fileCache ); 56 | $this->creditRepository = self::getContainer()->get( CreditRepository::class ); 57 | $this->epubCheck = self::getContainer()->get( EpubCheck::class ); 58 | } 59 | 60 | public function bookProvider() { 61 | return [ 62 | [ 'Around_the_Moon', 'en' ], 63 | [ 'Fables_de_La_Fontaine/édition_1874/Le_Philosophe_Scythe', 'fr' ] 64 | ]; 65 | } 66 | 67 | /** 68 | * @dataProvider bookProvider 69 | * @group exclude-from-ci 70 | */ 71 | public function testCreateBookEpub2( $title, $language ) { 72 | $epubFile = $this->createBook( $title, $language, 'epub-2' ); 73 | $this->checkEpub( $epubFile ); 74 | } 75 | 76 | /** 77 | * @dataProvider bookProvider 78 | */ 79 | public function testCreateBookEpub3( $title, $language ) { 80 | $epubFile = $this->createBook( $title, $language, 'epub-3' ); 81 | $this->checkEpub( $epubFile ); 82 | } 83 | 84 | /** 85 | * @dataProvider bookProvider 86 | */ 87 | public function testCreateBookMobi( $title, $language ) { 88 | $this->createBook( $title, $language, 'mobi' ); 89 | } 90 | 91 | private function createBook( $title, $language, $format ) { 92 | $this->api->setLang( $language ); 93 | $creator = BookCreator::forApi( $this->api, $format, [ 'credits' => false ], $this->generatorSelector, $this->creditRepository, $this->fileCache ); 94 | $creator->create( $title ); 95 | $this->assertFileExists( $creator->getFilePath() ); 96 | $this->assertNotNull( $creator->getBook() ); 97 | return $creator->getFilePath(); 98 | } 99 | 100 | /** 101 | * Check an epub and fail if there are any errors. 102 | * @param string $file 103 | */ 104 | private function checkEpub( string $file ) { 105 | foreach ( $this->epubCheck->check( $file ) as $result ) { 106 | if ( $result->isError() ) { 107 | $msg = $result->getMessage(); 108 | if ( $result->getLocations() ) { 109 | $msg .= ' -- Location 1 of ' . count( $result->getLocations() ) . ': ' . $result->getLocations()[0]; 110 | } 111 | $this->fail( $msg ); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/BookCreator/BookCreatorTest.php: -------------------------------------------------------------------------------- 1 | bookProvider = $observer = $this->getMockBuilder( BookProvider::class )->disableOriginalConstructor()->getMock(); 21 | $this->bookGenerator = $observer = $this->getMockBuilder( FormatGenerator::class )->getMock(); 22 | $this->bookCreator = new BookCreator( $this->bookProvider, $this->bookGenerator ); 23 | } 24 | 25 | public function testCreate() { 26 | $book = new Book(); 27 | $this->bookProvider->expects( $this->once() ) 28 | ->method( 'get' ) 29 | ->with( 'Test' ) 30 | ->willReturn( $book ); 31 | 32 | $this->bookGenerator->expects( $this->once() ) 33 | ->method( 'create' ) 34 | ->with( $book ) 35 | ->willReturn( 'path' ); 36 | 37 | $this->bookCreator->create( 'Test' ); 38 | $this->assertEquals( 'path', $this->bookCreator->getFilePath() ); 39 | $this->assertSame( $book, $this->bookCreator->getBook() ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Cli/BookCliTest.php: -------------------------------------------------------------------------------- 1 | mustRun(); 29 | $this->assertTrue( $process->isSuccessful() ); 30 | $this->assertFileExists( $outputPath . "/$title.epub" ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Generator/EpubGeneratorTest.php: -------------------------------------------------------------------------------- 1 | createMock( Api::class ), 22 | $this->createMock( Intuition::class ), 23 | new NullAdapter(), 24 | $this->createMock( FileCache::class ) 25 | ); 26 | } 27 | 28 | /** 29 | * @covers ::getCss 30 | */ 31 | public function testGetCss__withBookFont(): void { 32 | $book = new Book(); 33 | $bookFont = 'some-unique-book-font'; 34 | $book->options['fonts'] = $bookFont; 35 | 36 | $expectedCss = 'mary had a little lamb 341346 correct horse battery staple'; 37 | $fontProvider = $this->createMock( FontProvider::class ); 38 | $fontProvider 39 | ->expects( $this->atLeastOnce() ) 40 | ->method( 'getCss' ) 41 | ->with( $bookFont ) 42 | ->willReturn( $expectedCss ); 43 | $generator = $this->getEpubGenerator( $fontProvider ); 44 | $this->assertStringContainsString( 45 | $expectedCss, 46 | $generator->getCss( $book ) 47 | ); 48 | } 49 | 50 | /** 51 | * @covers ::getCss 52 | */ 53 | public function testGetCss__withoutBookFont(): void { 54 | $book = new Book(); 55 | 56 | $providerCss = 'mary had a little lamb 341346 correct horse battery staple'; 57 | $fontProvider = $this->createMock( FontProvider::class ); 58 | $fontProvider->expects( $this->never() )->method( 'getCss' )->willReturn( $providerCss ); 59 | $generator = $this->getEpubGenerator( $fontProvider ); 60 | // The soft expectation of $this->never() should already be enough anyway. 61 | $this->assertStringNotContainsString( 62 | $providerCss, 63 | $generator->getCss( $book ) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Http/StatTest.php: -------------------------------------------------------------------------------- 1 | request( 'GET', '/stat.php' ); 15 | $this->assertStringContainsString( 'Stats for ', $client->getResponse()->getContent() ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Repository/GeneratedBookRepositoryTest.php: -------------------------------------------------------------------------------- 1 | entityManager = self::getContainer()->get( 'doctrine' )->getManager(); 26 | $this->genBookRepo = $this->entityManager 27 | ->getRepository( GeneratedBook::class ); 28 | } 29 | 30 | /** 31 | * @covers \App\Repository\GeneratedBookRepository::getTypeAndLangStats 32 | */ 33 | public function testAddAndRetrieve() { 34 | // Make sure it's empty to start with. 35 | $this->assertSame( 36 | [], 37 | $this->genBookRepo->getTypeAndLangStats( date( 'n' ), date( 'Y' ) ) 38 | ); 39 | 40 | // Test book. 41 | $testBook = new Book(); 42 | $testBook->lang = 'en'; 43 | $testBook->title = 'Test "Book"'; 44 | 45 | // Add three entries. 46 | $this->entityManager->persist( new GeneratedBook( $testBook, 'epub' ) ); 47 | $this->entityManager->persist( new GeneratedBook( $testBook, 'pdf' ) ); 48 | $this->entityManager->persist( new GeneratedBook( $testBook, 'epub' ) ); 49 | $this->entityManager->flush(); 50 | 51 | // Test stats. 52 | $this->assertSame( 53 | [ 'epub' => [ 'en' => 2 ], 'pdf' => [ 'en' => 1 ] ], 54 | $this->genBookRepo->getTypeAndLangStats( date( 'n' ), date( 'Y' ) ) 55 | ); 56 | // Test data. 57 | /** @var GeneratedBook $firstLog */ 58 | $firstLog = $this->genBookRepo->findOneBy( [] ); 59 | $this->assertSame( 60 | 'Test "Book"', 61 | $firstLog->getTitle() 62 | ); 63 | } 64 | 65 | /** 66 | * Passing invalid strings for month or year shouldn't give incorrect results. 67 | * 68 | * @link https://phabricator.wikimedia.org/T290674 69 | * @covers \App\Repository\GeneratedBookRepository::getTypeAndLangStats() 70 | */ 71 | public function testInvalidDateParams(): void { 72 | $noStats = $this->genBookRepo->getTypeAndLangStats( 'foo', 'bar' ); 73 | $this->assertSame( [], $noStats ); 74 | 75 | $testBook = new Book(); 76 | $testBook->lang = 'en'; 77 | $testBook->title = 'Test "Book"'; 78 | $this->entityManager->persist( new GeneratedBook( $testBook, 'epub' ) ); 79 | $this->entityManager->flush(); 80 | 81 | $oneStat = $this->genBookRepo->getTypeAndLangStats( date( 'n' ), date( 'Y' ) ); 82 | $this->assertCount( 1, $oneStat ); 83 | 84 | $invalidMonthStat = $this->genBookRepo->getTypeAndLangStats( '09 AND 1=1', date( 'Y' ) ); 85 | $this->assertSame( [], $invalidMonthStat ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Util/ApiTest.php: -------------------------------------------------------------------------------- 1 | apiWithJsonResponse( [ 'result' => 'test' ] ); 22 | $result = $api->queryAsync( [ 'prop' => 'revisions' ] )->wait(); 23 | $this->assertEquals( [ 'result' => 'test' ], $result ); 24 | } 25 | 26 | public function testQueryAsyncThrowsExceptionOnInvalidJsonResponse() { 27 | $this->expectException( Exception::class ); 28 | $this->expectExceptionMessage( 'invalid JSON: "xxx-invalid": Syntax error' ); 29 | 30 | $api = $this->apiWithResponse( 200, [ 'Content-Type' => 'application/json' ], 'xxx-invalid' ); 31 | $api->queryAsync( [] )->wait(); 32 | } 33 | 34 | public function testQueryAsyncRaisesExceptionOnHttpError() { 35 | $this->expectException( ClientException::class ); 36 | $api = $this->apiWithResponse( 404, [], 'Not found' ); 37 | $api->queryAsync( [ 'prop' => 'revisions' ] )->wait(); 38 | } 39 | 40 | private function apiWithJsonResponse( $data ) { 41 | return $this->apiWithResponse( 200, [ 'Content-Type' => 'application/json' ], json_encode( $data ) ); 42 | } 43 | 44 | private function apiWithResponse( $status, $header, $body ) { 45 | $api = new Api( new NullLogger(), new NullAdapter(), new NullAdapter(), 60 ); 46 | $api->setClient( $this->mockClient( [ new Response( $status, $header, $body ) ] ) ); 47 | $api->setLang( 'en' ); 48 | return $api; 49 | } 50 | 51 | private function mockClient( $responses ) { 52 | return new Client( [ 'handler' => HandlerStack::create( new MockHandler( $responses ) ) ] ); 53 | } 54 | 55 | public function testGetAboutPage(): void { 56 | $api = $this->apiWithResponse( 200, [], 'Foo' ); 57 | $this->assertStringContainsString( 'Foo', $api->getAboutPage() ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Util/LoggingMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | performTestRequest( $logger, 28 | new Request( 'GET', 'http://example.com' ), 29 | new Response( 200 ) ); 30 | 31 | $this->assertEquals( 200, $response->getStatusCode() ); 32 | $this->assertCount( 1, $testHandler->getRecords() ); 33 | 34 | $record = $testHandler->getRecords()[0]; 35 | 36 | $this->assertEquals( 'DEBUG', $record['level_name'] ); 37 | $this->assertEquals( 'GET http://example.com', $record['message'] ); 38 | $this->assertEquals( [], $record['context'] ); 39 | } 40 | 41 | public function testNon2XXResponsesGetLoggedAsWarning() { 42 | $testHandler = new TestHandler(); 43 | $logger = new Logger( 'test', [ $testHandler ] ); 44 | 45 | $response = $this->performTestRequest( $logger, 46 | new Request( 'GET', 'http://example.com' ), 47 | new Response( 300 ) ); 48 | 49 | $this->assertEquals( 300, $response->getStatusCode() ); 50 | $this->assertCount( 2, $testHandler->getRecords() ); 51 | $this->assertEquals( 'HTTP response 300', $testHandler->getRecords()[1]['message'] ); 52 | $this->assertEquals( 'WARNING', $testHandler->getRecords()[1]['level_name'] ); 53 | } 54 | 55 | private function performTestRequest( $logger, $request, $response ) { 56 | $stack = new HandlerStack( new MockHandler( [ $response ] ) ); 57 | $stack->push( LoggingMiddleware::forLogger( $logger ) ); 58 | $handler = $stack->resolve(); 59 | return $handler( $request, [] )->wait(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Util/OnWikiConfigTest.php: -------------------------------------------------------------------------------- 1 | createMock( Api::class ); 19 | 20 | // Set up getDomainName() to return whatever was given to setLang(), for ease of testing. 21 | $lang = null; 22 | $api->expects( $this->exactly( 3 ) )->method( 'setLang' )->willReturnCallback( 23 | static function ( $arg ) use ( &$lang ) { 24 | $lang = $arg; 25 | } 26 | ); 27 | $api->expects( $this->exactly( 3 ) ) 28 | ->method( 'getDomainName' ) 29 | ->willReturnCallback( static function () use ( &$lang ) { 30 | return $lang; 31 | } ); 32 | 33 | // Set up the mock JSON string responses. 34 | $api->expects( $this->exactly( 3 ) ) 35 | ->method( 'get' ) 36 | ->willReturnMap( [ 37 | [ 'https://xxx/w/index.php?title=MediaWiki:WS_Export.json&action=raw&ctype=application/json', '', ], 38 | [ 'https://en/w/index.php?title=MediaWiki:WS_Export.json&action=raw&ctype=application/json', '', ], 39 | [ 'https://beta/w/index.php?title=MediaWiki:WS_Export.json&action=raw&ctype=application/json', '{"defaultFont": "Beta Font"}', ], 40 | ] ); 41 | 42 | $onWikiConfig = new OnWikiConfig( $api, new NullAdapter(), new Intuition() ); 43 | 44 | // Invalid language. 45 | $this->assertSame( '', $onWikiConfig->getDefaultFont( 'xxx' ) ); 46 | // English has no default font set. 47 | $this->assertSame( '', $onWikiConfig->getDefaultFont( 'en' ) ); 48 | // Beta has a default. 49 | $this->assertSame( 'Beta Font', $onWikiConfig->getDefaultFont( 'beta' ) ); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /tests/Util/Semaphore/UnixSemaphoreTest.php: -------------------------------------------------------------------------------- 1 | tryLock(); 18 | $this->assertInstanceOf( SemaphoreHandle::class, $handle ); 19 | $this->assertNull( $semaphore->tryLock() ); 20 | $handle->release(); 21 | $this->assertInstanceOf( SemaphoreHandle::class, $semaphore->tryLock() ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Util/UtilTest.php: -------------------------------------------------------------------------------- 1 | assertStringEndsWith( $expected, Util::encodeString( $input ) ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/WikidataTest.php: -------------------------------------------------------------------------------- 1 | wikidata = new Wikidata( new NullAdapter(), new NullLogger(), $client ); 20 | } 21 | 22 | /** 23 | * @covers \App\Wikidata::getWikisourceLangs() 24 | */ 25 | public function testLanguageList() { 26 | // Get the list in English and most are in their own language. 27 | $langs = $this->wikidata->getWikisourceLangs( 'en' ); 28 | $this->assertSame( 'svenskspråkiga Wikisource', $langs['sv'] ); 29 | $this->assertSame( 'English Wikisource', $langs['en'] ); 30 | $this->assertSame( 'Multilingual Wikisource', $langs['mul'] ); 31 | // Get the list in a different language and the only one changed should be mul. 32 | $langs2 = $this->wikidata->getWikisourceLangs( 'fr' ); 33 | $this->assertSame( 'English Wikisource', $langs2['en'] ); 34 | $this->assertSame( 'svenskspråkiga Wikisource', $langs2['sv'] ); 35 | $this->assertSame( 'Wikisource multilingue', $langs2['mul'] ); 36 | // Note that this test doesn't test the fallback to the interface language for missing local labels, 37 | // because this will hopefully be fixed on Wikidata and so wouldn't be repeatable. 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv( dirname( __DIR__ ) . '/.env' ); 8 | --------------------------------------------------------------------------------